Skip to content

Commit

Permalink
Merge pull request #449 from matrix-org/rav/handle_room_key_requests
Browse files Browse the repository at this point in the history
Process received room key requests
  • Loading branch information
richvdh committed Jun 6, 2017
2 parents ff323d0 + ab7e0a9 commit 1c66524
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 0 deletions.
6 changes: 6 additions & 0 deletions src/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ const SyncApi = require("./sync");
const MatrixBaseApis = require("./base-apis");
const MatrixError = httpApi.MatrixError;

import reEmit from './reemit';

const SCROLLBACK_DELAY_MS = 3000;
let CRYPTO_ENABLED = false;

Expand Down Expand Up @@ -166,6 +168,10 @@ function MatrixClient(opts) {
this.store,
opts.cryptoStore,
);
reEmit(this, this._crypto, [
"crypto.roomKeyRequest",
"crypto.roomKeyRequestCancellation",
]);

this.olmVersion = Crypto.getOlmVersion();
}
Expand Down
20 changes: 20 additions & 0 deletions src/crypto/algorithms/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,26 @@ class DecryptionAlgorithm {
importRoomKey(session) {
// ignore by default
}

/**
* Determine if we have the keys necessary to respond to a room key request
*
* @param {module:crypto#RoomKeyRequest} keyRequest
* @return {boolean} true if we have the keys and could (theoretically) share
* them; else false.
*/
hasKeysForKeyRequest(keyRequest) {
return false;
}

/**
* Send the response to a room key request
*
* @param {module:crypto#RoomKeyRequest} keyRequest
*/
shareKeysWithDevice(keyRequest) {
throw new Error("shareKeysWithDevice not supported for this DecryptionAlgorithm");
}
}
export {DecryptionAlgorithm}; // https://github.com/jsdoc3/jsdoc/issues/1272

Expand Down
167 changes: 167 additions & 0 deletions src/crypto/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ limitations under the License.

const anotherjson = require('another-json');
const q = require("q");
import {EventEmitter} from 'events';

const utils = require("../utils");
const OlmDevice = require("./OlmDevice");
Expand Down Expand Up @@ -99,6 +100,11 @@ function Crypto(baseApis, eventEmitter, sessionStore, userId, deviceId,
baseApis, this._deviceId, this._cryptoStore,
);

// list of IncomingRoomKeyRequests/IncomingRoomKeyRequestCancellations
// we received in the current sync.
this._receivedRoomKeyRequests = [];
this._receivedRoomKeyRequestCancellations = [];

let myDevices = this._sessionStore.getEndToEndDevicesForUser(
this._userId,
);
Expand All @@ -124,6 +130,8 @@ function Crypto(baseApis, eventEmitter, sessionStore, userId, deviceId,

_registerEventHandlers(this, eventEmitter);
}
utils.inherits(Crypto, EventEmitter);


function _registerEventHandlers(crypto, eventEmitter) {
eventEmitter.on("sync", function(syncState, oldState, data) {
Expand Down Expand Up @@ -157,6 +165,8 @@ function _registerEventHandlers(crypto, eventEmitter) {
crypto._onRoomKeyEvent(event);
} else if (event.getType() == "m.new_device") {
crypto._onNewDeviceEvent(event);
} else if (event.getType() == "m.room_key_request") {
crypto._onRoomKeyRequestEvent(event);
}
} catch (e) {
console.error("Error handling toDeviceEvent:", e);
Expand Down Expand Up @@ -893,6 +903,7 @@ Crypto.prototype._onSyncCompleted = function(syncData) {
// (https://github.com/vector-im/riot-web/issues/2782).
if (!syncData.catchingUp) {
_maybeUploadOneTimeKeys(this);
this._processReceivedRoomKeyRequests();
}
};

Expand Down Expand Up @@ -1081,6 +1092,106 @@ Crypto.prototype._onNewDeviceEvent = function(event) {
this._deviceList.invalidateUserDeviceList(userId);
};

/**
* Called when we get an m.room_key_request event.
*
* @private
* @param {module:models/event.MatrixEvent} event key request event
*/
Crypto.prototype._onRoomKeyRequestEvent = function(event) {
const content = event.getContent();
if (content.action === "request") {
// Queue it up for now, because they tend to arrive before the room state
// events at initial sync, and we want to see if we know anything about the
// room before passing them on to the app.
const req = new IncomingRoomKeyRequest(event);
this._receivedRoomKeyRequests.push(req);
} else if (content.action === "request_cancellation") {
const req = new IncomingRoomKeyRequestCancellation(event);
this._receivedRoomKeyRequestCancellations.push(req);
}
};

/**
* Process any m.room_key_request events which were queued up during the
* current sync.
*
* @private
*/
Crypto.prototype._processReceivedRoomKeyRequests = function() {
const requests = this._receivedRoomKeyRequests;
this._receivedRoomKeyRequests = [];
for (const req of requests) {
const userId = req.userId;
const deviceId = req.deviceId;

const body = req.requestBody;
const roomId = body.room_id;
const alg = body.algorithm;

console.log(`m.room_key_request from ${userId}:${deviceId}` +
` for ${roomId} / ${body.session_id} (id ${req.requestId})`);

if (userId !== this._userId) {
// TODO: determine if we sent this device the keys already: in
// which case we can do so again.
console.log("Ignoring room key request from other user for now");
return;
}

// todo: should we queue up requests we don't yet have keys for,
// in case they turn up later?

// if we don't have a decryptor for this room/alg, we don't have
// the keys for the requested events, and can drop the requests.
if (!this._roomDecryptors[roomId]) {
console.log(`room key request for unencrypted room ${roomId}`);
continue;
}

const decryptor = this._roomDecryptors[roomId][alg];
if (!decryptor) {
console.log(`room key request for unknown alg ${alg} in room ${roomId}`);
continue;
}

if (!decryptor.hasKeysForKeyRequest(req)) {
console.log(
`room key request for unknown session ${roomId} / ` +
body.session_id,
);
continue;
}

req.share = () => {
decryptor.shareKeysWithDevice(req);
};

// if the device is is verified already, share the keys
const device = this._deviceList.getStoredDevice(userId, deviceId);
if (device && device.isVerified()) {
console.log('device is already verified: sharing keys');
req.share();
return;
}

this.emit("crypto.roomKeyRequest", req);
}

const cancellations = this._receivedRoomKeyRequestCancellations;
this._receivedRoomKeyRequestCancellations = [];
for (const cancellation of cancellations) {
console.log(
`m.room_key_request cancellation for ${cancellation.userId}:` +
`${cancellation.deviceId} (id ${cancellation.requestId})`,
);

// we should probably only notify the app of cancellations we told it
// about, but we don't currently have a record of that, so we just pass
// everything through.
this.emit("crypto.roomKeyRequestCancellation", cancellation);
}
};

/**
* Get a decryptor for a given room and algorithm.
Expand Down Expand Up @@ -1159,5 +1270,61 @@ Crypto.prototype._signObject = function(obj) {
* @typedef {Object} RoomKeyRequestBody
*/

/**
* Represents a received m.room_key_request event
*
* @property {string} userId user requesting the key
* @property {string} deviceId device requesting the key
* @property {string} requestId unique id for the request
* @property {RoomKeyRequestBody} requestBody
* @property {Function} share callback which, when called, will ask
* the relevant crypto algorithm implementation to share the keys for
* this request.
*/
class IncomingRoomKeyRequest {
constructor(event) {
const content = event.getContent();

this.userId = event.getSender();
this.deviceId = content.requesting_device_id;
this.requestId = content.request_id;
this.requestBody = content.body || {};
this.share = () => {
throw new Error("don't know how to share keys for this request yet");
};
}
}

/**
* Represents a received m.room_key_request cancellation
*
* @property {string} userId user requesting the cancellation
* @property {string} deviceId device requesting the cancellation
* @property {string} requestId unique id for the request to be cancelled
*/
class IncomingRoomKeyRequestCancellation {
constructor(event) {
const content = event.getContent();

this.userId = event.getSender();
this.deviceId = content.requesting_device_id;
this.requestId = content.request_id;
}
}

/**
* Fires when we receive a room key request
*
* @event module:client~MatrixClient#"crypto.roomKeyRequest"
* @param {module:crypto~IncomingRoomKeyRequest} req request details
*/

/**
* Fires when we receive a room key request cancellation
*
* @event module:client~MatrixClient#"crypto.roomKeyRequestCancellation"
* @param {module:crypto~IncomingRoomKeyRequestCancellation} req
*/

/** */
module.exports = Crypto;

0 comments on commit 1c66524

Please sign in to comment.