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
Refactor decryption
Create the MatrixEvent wrapper before decryption, and then pass that into the decryptors, which should update it. Also remove the workaround that sends m.new_device messages when we get an unknown session; it's just a bandaid which is obscuring more meaningful problems.
This commit is contained in:
@@ -463,38 +463,31 @@ MatrixClient.prototype.isRoomEncrypted = function(roomId) {
|
|||||||
* Decrypt a received event according to the algorithm specified in the event.
|
* Decrypt a received event according to the algorithm specified in the event.
|
||||||
*
|
*
|
||||||
* @param {MatrixClient} client
|
* @param {MatrixClient} client
|
||||||
* @param {object} raw event
|
* @param {MatrixEvent} event
|
||||||
*
|
|
||||||
* @return {MatrixEvent}
|
|
||||||
*/
|
*/
|
||||||
function _decryptEvent(client, event) {
|
function _decryptEvent(client, event) {
|
||||||
if (!client._crypto) {
|
if (!client._crypto) {
|
||||||
return _badEncryptedMessage(event, "**Encryption not enabled**");
|
_badEncryptedMessage(event, "**Encryption not enabled**");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var decryptionResult;
|
|
||||||
try {
|
try {
|
||||||
decryptionResult = client._crypto.decryptEvent(event);
|
client._crypto.decryptEvent(event);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!(e instanceof Crypto.DecryptionError)) {
|
if (!(e instanceof Crypto.DecryptionError)) {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
return _badEncryptedMessage(event, "**" + e.message + "**");
|
_badEncryptedMessage(event, "**" + e.message + "**");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return new MatrixEvent(
|
|
||||||
event, decryptionResult.payload,
|
|
||||||
decryptionResult.keysProved,
|
|
||||||
decryptionResult.keysClaimed
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function _badEncryptedMessage(event, reason) {
|
function _badEncryptedMessage(event, reason) {
|
||||||
return new MatrixEvent(event, {
|
event.setClearData({
|
||||||
type: "m.room.message",
|
type: "m.room.message",
|
||||||
content: {
|
content: {
|
||||||
msgtype: "m.bad.encrypted",
|
msgtype: "m.bad.encrypted",
|
||||||
body: reason,
|
body: reason,
|
||||||
content: event.content,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -2829,10 +2822,11 @@ function _resolve(callback, defer, res) {
|
|||||||
|
|
||||||
function _PojoToMatrixEventMapper(client) {
|
function _PojoToMatrixEventMapper(client) {
|
||||||
function mapper(plainOldJsObject) {
|
function mapper(plainOldJsObject) {
|
||||||
if (plainOldJsObject.type === "m.room.encrypted") {
|
var event = new MatrixEvent(plainOldJsObject);
|
||||||
return _decryptEvent(client, plainOldJsObject);
|
if (event.isEncrypted()) {
|
||||||
|
_decryptEvent(client, event);
|
||||||
}
|
}
|
||||||
return new MatrixEvent(plainOldJsObject);
|
return event;
|
||||||
}
|
}
|
||||||
return mapper;
|
return mapper;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -451,7 +451,7 @@ utils.inherits(MegolmDecryption, base.DecryptionAlgorithm);
|
|||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*
|
*
|
||||||
* @param {object} event raw event
|
* @param {MatrixEvent} event
|
||||||
*
|
*
|
||||||
* @return {null} The event referred to an unknown megolm session
|
* @return {null} The event referred to an unknown megolm session
|
||||||
* @return {module:crypto.DecryptionResult} decryption result
|
* @return {module:crypto.DecryptionResult} decryption result
|
||||||
@@ -460,7 +460,7 @@ utils.inherits(MegolmDecryption, base.DecryptionAlgorithm);
|
|||||||
* problem decrypting the event
|
* problem decrypting the event
|
||||||
*/
|
*/
|
||||||
MegolmDecryption.prototype.decryptEvent = function(event) {
|
MegolmDecryption.prototype.decryptEvent = function(event) {
|
||||||
var content = event.content;
|
var content = event.getWireContent();
|
||||||
|
|
||||||
if (!content.sender_key || !content.session_id ||
|
if (!content.sender_key || !content.session_id ||
|
||||||
!content.ciphertext
|
!content.ciphertext
|
||||||
@@ -471,14 +471,15 @@ MegolmDecryption.prototype.decryptEvent = function(event) {
|
|||||||
var res;
|
var res;
|
||||||
try {
|
try {
|
||||||
res = this._olmDevice.decryptGroupMessage(
|
res = this._olmDevice.decryptGroupMessage(
|
||||||
event.room_id, content.sender_key, content.session_id, content.ciphertext
|
event.getRoomId(), content.sender_key, content.session_id, content.ciphertext
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new base.DecryptionError(e);
|
throw new base.DecryptionError(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res === null) {
|
if (res === null) {
|
||||||
return null;
|
// We've got a message for a session we don't have.
|
||||||
|
throw new base.DecryptionError("Unknown inbound session id");
|
||||||
}
|
}
|
||||||
|
|
||||||
var payload = JSON.parse(res.result);
|
var payload = JSON.parse(res.result);
|
||||||
@@ -486,17 +487,13 @@ MegolmDecryption.prototype.decryptEvent = function(event) {
|
|||||||
// belt-and-braces check that the room id matches that indicated by the HS
|
// belt-and-braces check that the room id matches that indicated by the HS
|
||||||
// (this is somewhat redundant, since the megolm session is scoped to the
|
// (this is somewhat redundant, since the megolm session is scoped to the
|
||||||
// room, so neither the sender nor a MITM can lie about the room_id).
|
// room, so neither the sender nor a MITM can lie about the room_id).
|
||||||
if (payload.room_id !== event.room_id) {
|
if (payload.room_id !== event.getRoomId()) {
|
||||||
throw new base.DecryptionError(
|
throw new base.DecryptionError(
|
||||||
"Message intended for room " + payload.room_id
|
"Message intended for room " + payload.room_id
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
event.setClearData(payload, res.keysClaimed, res.keysProved);
|
||||||
payload: payload,
|
|
||||||
keysClaimed: res.keysClaimed,
|
|
||||||
keysProved: res.keysProved,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -151,15 +151,13 @@ utils.inherits(OlmDecryption, base.DecryptionAlgorithm);
|
|||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*
|
*
|
||||||
* @param {object} event raw event
|
* @param {MatrixEvent} event
|
||||||
*
|
|
||||||
* @return {module:crypto.DecryptionResult} decryption result
|
|
||||||
*
|
*
|
||||||
* @throws {module:crypto/algorithms/base.DecryptionError} if there is a
|
* @throws {module:crypto/algorithms/base.DecryptionError} if there is a
|
||||||
* problem decrypting the event
|
* problem decrypting the event
|
||||||
*/
|
*/
|
||||||
OlmDecryption.prototype.decryptEvent = function(event) {
|
OlmDecryption.prototype.decryptEvent = function(event) {
|
||||||
var content = event.content;
|
var content = event.getWireContent();
|
||||||
var deviceKey = content.sender_key;
|
var deviceKey = content.sender_key;
|
||||||
var ciphertext = content.ciphertext;
|
var ciphertext = content.ciphertext;
|
||||||
|
|
||||||
@@ -178,7 +176,7 @@ OlmDecryption.prototype.decryptEvent = function(event) {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"Failed to decrypt Olm event (id=" +
|
"Failed to decrypt Olm event (id=" +
|
||||||
event.event_id + ") from " + deviceKey +
|
event.getId() + ") from " + deviceKey +
|
||||||
": " + e.message
|
": " + e.message
|
||||||
);
|
);
|
||||||
throw new base.DecryptionError("Bad Encrypted Message");
|
throw new base.DecryptionError("Bad Encrypted Message");
|
||||||
@@ -192,11 +190,11 @@ OlmDecryption.prototype.decryptEvent = function(event) {
|
|||||||
// older versions of riot did not set this field, so we cannot make
|
// older versions of riot did not set this field, so we cannot make
|
||||||
// this check. TODO: kill this off once our users have updated
|
// this check. TODO: kill this off once our users have updated
|
||||||
console.warn(
|
console.warn(
|
||||||
"Olm event (id=" + event.event_id + ") contains no 'recipient' " +
|
"Olm event (id=" + event.getId() + ") contains no 'recipient' " +
|
||||||
"property; cannot prevent unknown-key attack");
|
"property; cannot prevent unknown-key attack");
|
||||||
} else if (payload.recipient != this._userId) {
|
} else if (payload.recipient != this._userId) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"Event " + event.event_id + ": Intended recipient " +
|
"Event " + event.getId() + ": Intended recipient " +
|
||||||
payload.recipient + " does not match our id " + this._userId
|
payload.recipient + " does not match our id " + this._userId
|
||||||
);
|
);
|
||||||
throw new base.DecryptionError(
|
throw new base.DecryptionError(
|
||||||
@@ -207,12 +205,12 @@ OlmDecryption.prototype.decryptEvent = function(event) {
|
|||||||
if (payload.recipient_keys === undefined) {
|
if (payload.recipient_keys === undefined) {
|
||||||
// ditto
|
// ditto
|
||||||
console.warn(
|
console.warn(
|
||||||
"Olm event (id=" + event.event_id + ") contains no " +
|
"Olm event (id=" + event.getId() + ") contains no " +
|
||||||
"'recipient_keys' property; cannot prevent unknown-key attack");
|
"'recipient_keys' property; cannot prevent unknown-key attack");
|
||||||
} else if (payload.recipient_keys.ed25519 !=
|
} else if (payload.recipient_keys.ed25519 !=
|
||||||
this._olmDevice.deviceEd25519Key) {
|
this._olmDevice.deviceEd25519Key) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"Event " + event.event_id + ": Intended recipient ed25519 key " +
|
"Event " + event.getId() + ": Intended recipient ed25519 key " +
|
||||||
payload.recipient_keys.ed25519 + " did not match ours"
|
payload.recipient_keys.ed25519 + " did not match ours"
|
||||||
);
|
);
|
||||||
throw new base.DecryptionError("Message not intended for this device");
|
throw new base.DecryptionError("Message not intended for this device");
|
||||||
@@ -225,12 +223,12 @@ OlmDecryption.prototype.decryptEvent = function(event) {
|
|||||||
if (payload.sender === undefined) {
|
if (payload.sender === undefined) {
|
||||||
// ditto
|
// ditto
|
||||||
console.warn(
|
console.warn(
|
||||||
"Olm event (id=" + event.event_id + ") contains no " +
|
"Olm event (id=" + event.getId() + ") contains no " +
|
||||||
"'sender' property; cannot prevent unknown-key attack");
|
"'sender' property; cannot prevent unknown-key attack");
|
||||||
} else if (payload.sender != event.sender) {
|
} else if (payload.sender != event.getSender()) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"Event " + event.event_id + ": original sender " + payload.sender +
|
"Event " + event.getId() + ": original sender " + payload.sender +
|
||||||
" does not match reported sender " + event.sender
|
" does not match reported sender " + event.getSender()
|
||||||
);
|
);
|
||||||
throw new base.DecryptionError(
|
throw new base.DecryptionError(
|
||||||
"Message forwarded from " + payload.sender
|
"Message forwarded from " + payload.sender
|
||||||
@@ -238,9 +236,9 @@ OlmDecryption.prototype.decryptEvent = function(event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Olm events intended for a room have a room_id.
|
// Olm events intended for a room have a room_id.
|
||||||
if (payload.room_id !== event.room_id) {
|
if (payload.room_id !== event.getRoomId()) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"Event " + event.event_id + ": original room " + payload.room_id +
|
"Event " + event.getId() + ": original room " + payload.room_id +
|
||||||
" does not match reported room " + event.room_id
|
" does not match reported room " + event.room_id
|
||||||
);
|
);
|
||||||
throw new base.DecryptionError(
|
throw new base.DecryptionError(
|
||||||
@@ -248,12 +246,7 @@ OlmDecryption.prototype.decryptEvent = function(event) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
event.setClearData(payload, {curve25519: deviceKey}, payload.keys || {});
|
||||||
payload: payload,
|
|
||||||
sessionExists: true,
|
|
||||||
keysProved: {curve25519: deviceKey},
|
|
||||||
keysClaimed: payload.keys || {}
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -900,63 +900,17 @@ Crypto.prototype.encryptEventIfNeeded = function(event, room) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} module:crypto.DecryptionResult
|
|
||||||
*
|
|
||||||
* @property {Object} payload decrypted payload (with properties 'type',
|
|
||||||
* 'content').
|
|
||||||
*
|
|
||||||
* @property {Object<string, string>} keysClaimed keys that the sender of the
|
|
||||||
* event claims ownership of: map from key type to base64-encoded key
|
|
||||||
*
|
|
||||||
* @property {Object<string, string>} keysProved keys that the sender of the
|
|
||||||
* event is known to have ownership of: map from key type to base64-encoded
|
|
||||||
* key
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt a received event
|
* Decrypt a received event
|
||||||
*
|
*
|
||||||
* @param {object} event raw event
|
* @param {MatrixEvent} event
|
||||||
*
|
|
||||||
* @return {module:crypto.DecryptionResult} decryption result
|
|
||||||
*
|
*
|
||||||
* @raises {algorithms.DecryptionError} if there is a problem decrypting the event
|
* @raises {algorithms.DecryptionError} if there is a problem decrypting the event
|
||||||
*/
|
*/
|
||||||
Crypto.prototype.decryptEvent = function(event) {
|
Crypto.prototype.decryptEvent = function(event) {
|
||||||
var content = event.content;
|
var content = event.getWireContent();
|
||||||
var alg = this._getRoomDecryptor(event.room_id, content.algorithm);
|
var alg = this._getRoomDecryptor(event.getRoomId(), content.algorithm);
|
||||||
var r = alg.decryptEvent(event);
|
alg.decryptEvent(event);
|
||||||
|
|
||||||
if (r !== null) {
|
|
||||||
return r;
|
|
||||||
} else {
|
|
||||||
// We've got a message for a session we don't have. Maybe the sender
|
|
||||||
// forgot to tell us about the session. Remind the sender that we
|
|
||||||
// exist so that they might tell us about the session on their next
|
|
||||||
// send.
|
|
||||||
//
|
|
||||||
// (Alternatively, it might be that we are just looking at
|
|
||||||
// scrollback... at least we rate-limit the m.new_device events :/)
|
|
||||||
//
|
|
||||||
// XXX: this is a band-aid which masks symptoms of other bugs. It would
|
|
||||||
// be nice to get rid of it.
|
|
||||||
if (event.room_id !== undefined && event.sender !== undefined) {
|
|
||||||
var device_id = event.content.device_id;
|
|
||||||
if (device_id === undefined) {
|
|
||||||
// if the sending device didn't tell us its device_id, fall
|
|
||||||
// back to all devices.
|
|
||||||
device_id = null;
|
|
||||||
}
|
|
||||||
console.log(
|
|
||||||
"Unknown session decrypting event id " + event.event_id +
|
|
||||||
": sending m.new_device event"
|
|
||||||
);
|
|
||||||
this._sendPingToDevice(event.sender, device_id, event.room_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new algorithms.DecryptionError("Unknown inbound session id");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -51,15 +51,6 @@ module.exports.EventStatus = {
|
|||||||
*
|
*
|
||||||
* @param {Object} event The raw event to be wrapped in this DAO
|
* @param {Object} event The raw event to be wrapped in this DAO
|
||||||
*
|
*
|
||||||
* @param {Object=} clearEvent For encrypted events, the plaintext payload for
|
|
||||||
* the event (typically containing <tt>type</tt> and <tt>content</tt> fields).
|
|
||||||
*
|
|
||||||
* @param {Object=} keysProved Keys owned by the sender of this event.
|
|
||||||
* See {@link module:models/event.MatrixEvent#getKeysProved}.
|
|
||||||
*
|
|
||||||
* @param {Object=} keysClaimed Keys the sender of this event claims.
|
|
||||||
* See {@link module:models/event.MatrixEvent#getKeysClaimed}.
|
|
||||||
*
|
|
||||||
* @prop {Object} event The raw (possibly encrypted) event. <b>Do not access
|
* @prop {Object} event The raw (possibly encrypted) event. <b>Do not access
|
||||||
* this property</b> directly unless you absolutely have to. Prefer the getter
|
* this property</b> directly unless you absolutely have to. Prefer the getter
|
||||||
* methods defined on this class. Using the getter methods shields your app
|
* methods defined on this class. Using the getter methods shields your app
|
||||||
@@ -75,19 +66,18 @@ module.exports.EventStatus = {
|
|||||||
* Default: true. <strong>This property is experimental and may change.</strong>
|
* Default: true. <strong>This property is experimental and may change.</strong>
|
||||||
*/
|
*/
|
||||||
module.exports.MatrixEvent = function MatrixEvent(
|
module.exports.MatrixEvent = function MatrixEvent(
|
||||||
event, clearEvent, keysProved, keysClaimed
|
event
|
||||||
) {
|
) {
|
||||||
this.event = event || {};
|
this.event = event || {};
|
||||||
this.sender = null;
|
this.sender = null;
|
||||||
this.target = null;
|
this.target = null;
|
||||||
this.status = null;
|
this.status = null;
|
||||||
this.forwardLooking = true;
|
this.forwardLooking = true;
|
||||||
|
|
||||||
this._clearEvent = clearEvent || {};
|
|
||||||
this._pushActions = null;
|
this._pushActions = null;
|
||||||
|
|
||||||
this._keysProved = keysProved || {};
|
this._clearEvent = {};
|
||||||
this._keysClaimed = keysClaimed || {};
|
this._keysProved = {};
|
||||||
|
this._keysClaimed = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.MatrixEvent.prototype = {
|
module.exports.MatrixEvent.prototype = {
|
||||||
@@ -239,12 +229,36 @@ module.exports.MatrixEvent.prototype = {
|
|||||||
this._keysClaimed = keys;
|
this._keysClaimed = keys;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the cleartext data on this event.
|
||||||
|
*
|
||||||
|
* (This is used after decrypting an event; it should not be used by applications).
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @fires module:models/event.MatrixEvent#"Event.decrypted"
|
||||||
|
*
|
||||||
|
* @param {Object} clearEvent The plaintext payload for the event
|
||||||
|
* (typically containing <tt>type</tt> and <tt>content</tt> fields).
|
||||||
|
*
|
||||||
|
* @param {Object=} keysProved Keys owned by the sender of this event.
|
||||||
|
* See {@link module:models/event.MatrixEvent#getKeysProved}.
|
||||||
|
*
|
||||||
|
* @param {Object=} keysClaimed Keys the sender of this event claims.
|
||||||
|
* See {@link module:models/event.MatrixEvent#getKeysClaimed}.
|
||||||
|
*/
|
||||||
|
setClearData: function(clearEvent, keysProved, keysClaimed) {
|
||||||
|
this._clearEvent = clearEvent;
|
||||||
|
this._keysProved = keysProved || {};
|
||||||
|
this._keysClaimed = keysClaimed || {};
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the event is encrypted.
|
* Check if the event is encrypted.
|
||||||
* @return {boolean} True if this event is encrypted.
|
* @return {boolean} True if this event is encrypted.
|
||||||
*/
|
*/
|
||||||
isEncrypted: function() {
|
isEncrypted: function() {
|
||||||
return Boolean(this._clearEvent.type);
|
return this.event.type === "m.room.encrypted";
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user