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
Add support for forwarding room keys to megolm
when we receive a m.forwarded_room_key, add it to the crypto store, but remember who forwarded it to us, so we can decide whether to trust them separately.
This commit is contained in:
@@ -5,6 +5,7 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
import expect from 'expect';
|
import expect from 'expect';
|
||||||
|
import q from 'q';
|
||||||
|
|
||||||
import sdk from '../../../..';
|
import sdk from '../../../..';
|
||||||
import algorithms from '../../../../lib/crypto/algorithms';
|
import algorithms from '../../../../lib/crypto/algorithms';
|
||||||
@@ -105,5 +106,55 @@ describe("MegolmDecryption", function() {
|
|||||||
megolmDecryption.decryptEvent(event);
|
megolmDecryption.decryptEvent(event);
|
||||||
expect(event.getContent()).toEqual('testytest');
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ function checkPayloadLength(payloadString) {
|
|||||||
*
|
*
|
||||||
* @typedef {Object} module:crypto/OlmDevice.MegolmSessionData
|
* @typedef {Object} module:crypto/OlmDevice.MegolmSessionData
|
||||||
* @property {String} sender_key Sender's Curve25519 device key
|
* @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<string, string>} sender_claimed_keys Other keys the sender claims.
|
* @property {Object<string, string>} sender_claimed_keys Other keys the sender claims.
|
||||||
* @property {String} room_id Room this session is used in
|
* @property {String} room_id Room this session is used in
|
||||||
* @property {String} session_id Unique id for the session
|
* @property {String} session_id Unique id for the session
|
||||||
@@ -587,6 +589,8 @@ OlmDevice.prototype.getOutboundGroupSessionKey = function(sessionId) {
|
|||||||
* @property {string} room_Id
|
* @property {string} room_Id
|
||||||
* @property {string} session pickled Olm.InboundGroupSession
|
* @property {string} session pickled Olm.InboundGroupSession
|
||||||
* @property {Object<string, string>} keysClaimed
|
* @property {Object<string, string>} keysClaimed
|
||||||
|
* @property {[string]} forwardingCurve25519KeyChain Devices involved in forwarding
|
||||||
|
* this session to us (normally empty).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -657,12 +661,18 @@ OlmDevice.prototype._getInboundGroupSession = function(
|
|||||||
*
|
*
|
||||||
* @param {string} roomId room in which this session will be used
|
* @param {string} roomId room in which this session will be used
|
||||||
* @param {string} senderKey base64-encoded curve25519 key of the sender
|
* @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} sessionId session identifier
|
||||||
* @param {string} sessionKey base64-encoded secret key
|
* @param {string} sessionKey base64-encoded secret key
|
||||||
* @param {Object<string, string>} keysClaimed Other keys the sender claims.
|
* @param {Object<string, string>} 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(
|
OlmDevice.prototype.addInboundGroupSession = function(
|
||||||
roomId, senderKey, sessionId, sessionKey, keysClaimed,
|
roomId, senderKey, forwardingCurve25519KeyChain,
|
||||||
|
sessionId, sessionKey, keysClaimed,
|
||||||
|
exportFormat,
|
||||||
) {
|
) {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
@@ -685,7 +695,11 @@ OlmDevice.prototype.addInboundGroupSession = function(
|
|||||||
// new session.
|
// new session.
|
||||||
const session = new Olm.InboundGroupSession();
|
const session = new Olm.InboundGroupSession();
|
||||||
try {
|
try {
|
||||||
session.create(sessionKey);
|
if (exportFormat) {
|
||||||
|
session.import_session(sessionKey);
|
||||||
|
} else {
|
||||||
|
session.create(sessionKey);
|
||||||
|
}
|
||||||
if (sessionId != session.session_id()) {
|
if (sessionId != session.session_id()) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Mismatched group session ID from senderKey: " + senderKey,
|
"Mismatched group session ID from senderKey: " + senderKey,
|
||||||
@@ -696,6 +710,7 @@ OlmDevice.prototype.addInboundGroupSession = function(
|
|||||||
room_id: roomId,
|
room_id: roomId,
|
||||||
session: session.pickle(this._pickleKey),
|
session: session.pickle(this._pickleKey),
|
||||||
keysClaimed: keysClaimed,
|
keysClaimed: keysClaimed,
|
||||||
|
forwardingCurve25519KeyChain: forwardingCurve25519KeyChain,
|
||||||
};
|
};
|
||||||
|
|
||||||
self._saveInboundGroupSession(
|
self._saveInboundGroupSession(
|
||||||
@@ -765,8 +780,9 @@ OlmDevice.prototype.importInboundGroupSession = function(data) {
|
|||||||
*
|
*
|
||||||
* @return {null} the sessionId is unknown
|
* @return {null} the sessionId is unknown
|
||||||
*
|
*
|
||||||
* @return {{result: string, senderKey: string, keysClaimed:
|
* @return {{result: string, senderKey: string,
|
||||||
* Object<string, string>}} result
|
* forwardingCurve25519KeyChain: [string],
|
||||||
|
* keysClaimed: Object<string, string>}}
|
||||||
*/
|
*/
|
||||||
OlmDevice.prototype.decryptGroupMessage = function(
|
OlmDevice.prototype.decryptGroupMessage = function(
|
||||||
roomId, senderKey, sessionId, body,
|
roomId, senderKey, sessionId, body,
|
||||||
@@ -800,6 +816,7 @@ OlmDevice.prototype.decryptGroupMessage = function(
|
|||||||
result: plaintext,
|
result: plaintext,
|
||||||
keysClaimed: sessionData.keysClaimed || {},
|
keysClaimed: sessionData.keysClaimed || {},
|
||||||
senderKey: senderKey,
|
senderKey: senderKey,
|
||||||
|
forwardingCurve25519KeyChain: sessionData.forwardingCurve25519KeyChain || [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -846,16 +863,26 @@ OlmDevice.prototype.hasInboundSessionKeys = function(roomId, senderKey, sessionI
|
|||||||
* @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
|
||||||
*
|
*
|
||||||
* @returns {{chain_index: number, key: string}} details of the session key. The
|
* @returns {{chain_index: number, key: string,
|
||||||
* key is a base64-encoded megolm key in export format.
|
* 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) {
|
OlmDevice.prototype.getInboundGroupSessionKey = function(roomId, senderKey, sessionId) {
|
||||||
function getKey(session, sessionData) {
|
function getKey(session, sessionData) {
|
||||||
const messageIndex = session.first_known_index();
|
const messageIndex = session.first_known_index();
|
||||||
|
|
||||||
|
const claimedKeys = sessionData.keysClaimed || {};
|
||||||
|
const senderEd25519Key = claimedKeys.ed25519 || null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"chain_index": messageIndex,
|
"chain_index": messageIndex,
|
||||||
"key": session.export_session(messageIndex),
|
"key": session.export_session(messageIndex),
|
||||||
|
"forwarding_curve25519_key_chain":
|
||||||
|
sessionData.forwardingCurve25519KeyChain || [],
|
||||||
|
"sender_claimed_ed25519_key": senderEd25519Key,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -894,6 +921,8 @@ OlmDevice.prototype.exportInboundGroupSession = function(senderKey, sessionId) {
|
|||||||
"room_id": r.room_id,
|
"room_id": r.room_id,
|
||||||
"session_id": sessionId,
|
"session_id": sessionId,
|
||||||
"session_key": session.export_session(messageIndex),
|
"session_key": session.export_session(messageIndex),
|
||||||
|
"forwarding_curve25519_key_chain":
|
||||||
|
session.forwardingCurve25519KeyChain || [],
|
||||||
};
|
};
|
||||||
} finally {
|
} finally {
|
||||||
session.free();
|
session.free();
|
||||||
|
|||||||
@@ -250,7 +250,7 @@ MegolmEncryption.prototype._prepareNewSession = function() {
|
|||||||
const key = this._olmDevice.getOutboundGroupSessionKey(sessionId);
|
const key = this._olmDevice.getOutboundGroupSessionKey(sessionId);
|
||||||
|
|
||||||
this._olmDevice.addInboundGroupSession(
|
this._olmDevice.addInboundGroupSession(
|
||||||
this._roomId, this._olmDevice.deviceCurve25519Key, sessionId,
|
this._roomId, this._olmDevice.deviceCurve25519Key, [], sessionId,
|
||||||
key.key, {ed25519: this._olmDevice.deviceEd25519Key},
|
key.key, {ed25519: this._olmDevice.deviceEd25519Key},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -595,7 +595,8 @@ MegolmDecryption.prototype._decryptEvent = function(event, requestKeysOnFail) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
event.setClearData(payload, res.senderKey, res.keysClaimed.ed25519);
|
event.setClearData(payload, res.senderKey, res.keysClaimed.ed25519,
|
||||||
|
res.forwardingCurve25519KeyChain);
|
||||||
};
|
};
|
||||||
|
|
||||||
MegolmDecryption.prototype._requestKeysForEvent = function(event) {
|
MegolmDecryption.prototype._requestKeysForEvent = function(event) {
|
||||||
@@ -645,8 +646,11 @@ MegolmDecryption.prototype._addEventToPendingList = function(event) {
|
|||||||
*/
|
*/
|
||||||
MegolmDecryption.prototype.onRoomKeyEvent = function(event) {
|
MegolmDecryption.prototype.onRoomKeyEvent = function(event) {
|
||||||
const content = event.getContent();
|
const content = event.getContent();
|
||||||
const senderKey = event.getSenderKey();
|
|
||||||
const sessionId = content.session_id;
|
const sessionId = content.session_id;
|
||||||
|
let senderKey = event.getSenderKey();
|
||||||
|
let forwardingKeyChain = [];
|
||||||
|
let exportFormat = false;
|
||||||
|
let keysClaimed;
|
||||||
|
|
||||||
if (!content.room_id ||
|
if (!content.room_id ||
|
||||||
!sessionId ||
|
!sessionId ||
|
||||||
@@ -655,15 +659,49 @@ MegolmDecryption.prototype.onRoomKeyEvent = function(event) {
|
|||||||
console.error("key event is missing fields");
|
console.error("key event is missing fields");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!senderKey) {
|
if (!senderKey) {
|
||||||
console.error("key event has no sender key (not encrypted?)");
|
console.error("key event has no sender key (not encrypted?)");
|
||||||
return;
|
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}`);
|
console.log(`Adding key for megolm session ${senderKey}|${sessionId}`);
|
||||||
this._olmDevice.addInboundGroupSession(
|
this._olmDevice.addInboundGroupSession(
|
||||||
content.room_id, senderKey, sessionId,
|
content.room_id, senderKey, forwardingKeyChain, sessionId,
|
||||||
content.session_key, event.getKeysClaimed(),
|
content.session_key, keysClaimed,
|
||||||
|
exportFormat,
|
||||||
);
|
);
|
||||||
|
|
||||||
// cancel any outstanding room key requests for this session
|
// cancel any outstanding room key requests for this session
|
||||||
@@ -722,22 +760,10 @@ MegolmDecryption.prototype.shareKeysWithDevice = function(keyRequest) {
|
|||||||
+ userId + ":" + deviceId,
|
+ userId + ":" + deviceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const key = this._olmDevice.getInboundGroupSessionKey(
|
const payload = this._buildKeyForwardingMessage(
|
||||||
body.room_id, body.sender_key, body.session_id,
|
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 = {
|
const encryptedContent = {
|
||||||
algorithm: olmlib.OLM_ALGORITHM,
|
algorithm: olmlib.OLM_ALGORITHM,
|
||||||
sender_key: this._olmDevice.deviceCurve25519Key,
|
sender_key: this._olmDevice.deviceCurve25519Key,
|
||||||
@@ -765,6 +791,27 @@ MegolmDecryption.prototype.shareKeysWithDevice = function(keyRequest) {
|
|||||||
}).done();
|
}).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
|
* @inheritdoc
|
||||||
|
|||||||
@@ -161,7 +161,8 @@ function _registerEventHandlers(crypto, eventEmitter) {
|
|||||||
|
|
||||||
eventEmitter.on("toDeviceEvent", function(event) {
|
eventEmitter.on("toDeviceEvent", function(event) {
|
||||||
try {
|
try {
|
||||||
if (event.getType() == "m.room_key") {
|
if (event.getType() == "m.room_key"
|
||||||
|
|| event.getType() == "m.forwarded_room_key") {
|
||||||
crypto._onRoomKeyEvent(event);
|
crypto._onRoomKeyEvent(event);
|
||||||
} else if (event.getType() == "m.new_device") {
|
} else if (event.getType() == "m.new_device") {
|
||||||
crypto._onNewDeviceEvent(event);
|
crypto._onNewDeviceEvent(event);
|
||||||
|
|||||||
Reference in New Issue
Block a user