1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-12-07 05:22:15 +03:00

Make crypto.decryptMessage return decryption results

... instead of having it call event.setClearData.

The main advantage of this is that it fixes a race condition, wherein apps
could see `event.isDecrypting()` to be true, but in fact the event had been
decrypted (and there was no `Event.decrypted` event on its way).

We're also fixing another race, wherein if the first attempt to decrypt failed,
a call to `attemptDecryption` would race against the first call and a second
attempt to decrypt would never happen.

This also gives a cleaner interface to MatrixEvent, at the expense of making
the `megolm` unit test a bit more hoop-jumpy.
This commit is contained in:
Richard van der Hoff
2017-08-09 17:34:45 +01:00
parent 9550bca099
commit 6613ee6b0d
7 changed files with 227 additions and 83 deletions

View File

@@ -25,8 +25,6 @@ const EventEmitter = require("events").EventEmitter;
const utils = require('../utils.js');
import Promise from 'bluebird';
/**
* Enum for event statuses.
* @readonly
@@ -326,13 +324,12 @@ utils.extend(module.exports.MatrixEvent.prototype, {
* @internal
*
* @param {module:crypto} crypto crypto module
*
* returns a promise which resolves (to undefined) when the decryption
* attempt is completed.
*/
attemptDecryption: function(crypto) {
if (!crypto) {
this._badEncryptedMessage("Encryption not enabled");
return;
}
attemptDecryption: async function(crypto) {
// start with a couple of sanity checks.
if (!this.isEncrypted()) {
throw new Error("Attempt to decrypt event which isn't encrypted");
}
@@ -347,6 +344,12 @@ utils.extend(module.exports.MatrixEvent.prototype, {
);
}
// if we already have a decryption attempt in progress, then it may
// fail because it was using outdated info. We now have reason to
// succeed where it failed before, but we don't want to have multiple
// attempts going at the same time, so just set a flag that says we have
// new info.
//
if (this._decrypting) {
console.log(
`Event ${this.getId()} already being decrypted; queueing a retry`,
@@ -356,51 +359,86 @@ utils.extend(module.exports.MatrixEvent.prototype, {
}
this._decrypting = true;
this._doDecryption(crypto).finally(() => {
this._decrypting = false;
while (true) {
this._retryDecryption = false;
});
},
_doDecryption: function(crypto) {
return Promise.try(() => {
return crypto.decryptEvent(this);
}).catch((e) => {
if (e.name !== "DecryptionError") {
// not a decryption error: log the whole exception as an error.
console.error(
`Error decrypting event (id=${this.getId()}): ${e.stack || e}`,
);
return null;
} else if (this._retryDecryption) {
// decryption error, but we have a retry queued.
console.log(
`Got error decrypting event (id=${this.getId()}), but retrying`,
);
this._retryDecryption = false;
return this._doDecryption(crypto);
} else {
let res;
try {
if (!crypto) {
res = this._badEncryptedMessage("Encryption not enabled");
} else {
res = await crypto.decryptEvent(this);
}
} catch (e) {
if (e.name !== "DecryptionError") {
// not a decryption error: log the whole exception as an error
// (and don't bother with a retry)
console.error(
`Error decrypting event (id=${this.getId()}): ${e.stack || e}`,
);
this._decrypting = false;
this._retryDecryption = false;
return;
}
// see if we have a retry queued.
//
// NB: make sure to keep this check in the same tick of the
// event loop as `_decrypting = false` below - otherwise we
// risk a race:
//
// * A: we check _retryDecryption here and see that it is
// false
// * B: we get a second call to attemptDecryption, which sees
// that _decrypting is true so sets _retryDecryption
// * A: we continue below, clear _decrypting, and never do
// the retry.
//
if (this._retryDecryption) {
// decryption error, but we have a retry queued.
console.log(
`Got error decrypting event (id=${this.getId()}: ` +
`${e.message}), but retrying`,
);
continue;
}
// decryption error, no retries queued. Warn about the error and
// set it to m.bad.encrypted.
console.warn(
`Error decrypting event (id=${this.getId()}): ${e}`,
);
this._badEncryptedMessage(e.message);
return null;
res = this._badEncryptedMessage(e.message);
}
});
// at this point, we've either successfully decrypted the event, or have given up
// (and set res to a 'badEncryptedMessage'). Either way, we can now set the
// cleartext of the event and raise Event.decrypted.
//
// make sure we clear '_decrypting' before sending the 'Event.decrypted' event,
// otherwise the app will be confused to see `isBeingDecrypted` still set when
// there isn't an `Event.decrypted` on the way.
//
// see also notes on _retryDecryption above.
//
this._decrypting = false;
this._retryDecryption = false;
this._setClearData(res);
return;
}
},
_badEncryptedMessage: function(reason) {
this.setClearData({
type: "m.room.message",
content: {
msgtype: "m.bad.encrypted",
body: "** Unable to decrypt: " + reason + " **",
return {
clearEvent: {
type: "m.room.message",
content: {
msgtype: "m.bad.encrypted",
body: "** Unable to decrypt: " + reason + " **",
},
},
});
};
},
/**
@@ -412,29 +450,17 @@ utils.extend(module.exports.MatrixEvent.prototype, {
*
* @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 {string=} senderCurve25519Key Key owned by the sender of this event.
* See {@link module:models/event.MatrixEvent#getSenderKey}.
*
* @param {string=} claimedEd25519Key ed25519 key claimed by the sender of
* this event. See {@link module:models/event.MatrixEvent#getClaimedEd25519Key}.
*
* @param {Array<string>=} forwardingCurve25519KeyChain list of curve25519 keys
* involved in telling us about the senderCurve25519Key and claimedEd25519Key.
* See {@link module:models/event.MatrixEvent#getForwardingCurve25519KeyChain}.
* @param {module:crypto~EventDecryptionResult} decryptionResult
* the decryption result, including the plaintext and some key info
*/
setClearData: function(
clearEvent,
senderCurve25519Key,
claimedEd25519Key,
forwardingCurve25519KeyChain,
) {
this._clearEvent = clearEvent;
this._senderCurve25519Key = senderCurve25519Key || null;
this._claimedEd25519Key = claimedEd25519Key || null;
this._forwardingCurve25519KeyChain = forwardingCurve25519KeyChain || [];
_setClearData: function(decryptionResult) {
this._clearEvent = decryptionResult.clearEvent;
this._senderCurve25519Key =
decryptionResult.senderCurve25519Key || null;
this._claimedEd25519Key =
decryptionResult.claimedEd25519Key || null;
this._forwardingCurve25519KeyChain =
decryptionResult.forwardingCurve25519KeyChain || [];
this.emit("Event.decrypted", this);
},