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
refactor key sharing requests
use sendRoomKeyRequest with a new resend flag, instead of cancelRoomKeyRequest, when requesting keys, so that we make sure that we send a new request if there is no previous request fixes https://github.com/vector-im/riot-web/issues/6838
This commit is contained in:
@@ -273,5 +273,29 @@ describe("Crypto", function() {
|
|||||||
.toNotExist();
|
.toNotExist();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
it("creates a new keyshare request if we request a keyshare", async function() {
|
||||||
|
// make sure that cancelAndResend... creates a new keyshare request
|
||||||
|
// if there wasn't an already-existing one
|
||||||
|
const event = new MatrixEvent({
|
||||||
|
sender: "@bob:example.com",
|
||||||
|
room_id: "!someroom",
|
||||||
|
content: {
|
||||||
|
algorithm: olmlib.MEGOLM_ALGORITHM,
|
||||||
|
session_id: "sessionid",
|
||||||
|
sender_key: "senderkey",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await aliceClient.cancelAndResendEventRoomKeyRequest(event);
|
||||||
|
const cryptoStore = aliceClient._cryptoStore;
|
||||||
|
const roomKeyRequestBody = {
|
||||||
|
algorithm: olmlib.MEGOLM_ALGORITHM,
|
||||||
|
room_id: "!someroom",
|
||||||
|
session_id: "sessionid",
|
||||||
|
sender_key: "senderkey",
|
||||||
|
};
|
||||||
|
expect(await cryptoStore.getOutgoingRoomKeyRequest(roomKeyRequestBody))
|
||||||
|
.toExist();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -760,9 +760,10 @@ MatrixClient.prototype.isEventSenderVerified = async function(event) {
|
|||||||
* request.
|
* request.
|
||||||
* @param {MatrixEvent} event event of which to cancel and resend the room
|
* @param {MatrixEvent} event event of which to cancel and resend the room
|
||||||
* key request.
|
* key request.
|
||||||
|
* @return {Promise} A promise that will resolve when the key request is queued
|
||||||
*/
|
*/
|
||||||
MatrixClient.prototype.cancelAndResendEventRoomKeyRequest = function(event) {
|
MatrixClient.prototype.cancelAndResendEventRoomKeyRequest = function(event) {
|
||||||
event.cancelAndResendKeyRequest(this._crypto);
|
return event.cancelAndResendKeyRequest(this._crypto, this.getUserId());
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -124,35 +124,115 @@ export default class OutgoingRoomKeyRequestManager {
|
|||||||
*
|
*
|
||||||
* @param {module:crypto~RoomKeyRequestBody} requestBody
|
* @param {module:crypto~RoomKeyRequestBody} requestBody
|
||||||
* @param {Array<{userId: string, deviceId: string}>} recipients
|
* @param {Array<{userId: string, deviceId: string}>} recipients
|
||||||
|
* @param {boolean} resend whether to resend the key request if there is
|
||||||
|
* already one
|
||||||
*
|
*
|
||||||
* @returns {Promise} resolves when the request has been added to the
|
* @returns {Promise} resolves when the request has been added to the
|
||||||
* pending list (or we have established that a similar request already
|
* pending list (or we have established that a similar request already
|
||||||
* exists)
|
* exists)
|
||||||
*/
|
*/
|
||||||
sendRoomKeyRequest(requestBody, recipients) {
|
async sendRoomKeyRequest(requestBody, recipients, resend=false) {
|
||||||
return this._cryptoStore.getOrAddOutgoingRoomKeyRequest({
|
const req = await this._cryptoStore.getOutgoingRoomKeyRequest(
|
||||||
|
requestBody,
|
||||||
|
);
|
||||||
|
if (!req) {
|
||||||
|
await this._cryptoStore.getOrAddOutgoingRoomKeyRequest({
|
||||||
requestBody: requestBody,
|
requestBody: requestBody,
|
||||||
recipients: recipients,
|
recipients: recipients,
|
||||||
requestId: this._baseApis.makeTxnId(),
|
requestId: this._baseApis.makeTxnId(),
|
||||||
state: ROOM_KEY_REQUEST_STATES.UNSENT,
|
state: ROOM_KEY_REQUEST_STATES.UNSENT,
|
||||||
}).then((req) => {
|
|
||||||
if (req.state === ROOM_KEY_REQUEST_STATES.UNSENT) {
|
|
||||||
this._startTimer();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
switch (req.state) {
|
||||||
|
case ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING_AND_WILL_RESEND:
|
||||||
|
case ROOM_KEY_REQUEST_STATES.UNSENT:
|
||||||
|
// nothing to do here, since we're going to send a request anyways
|
||||||
|
return;
|
||||||
|
|
||||||
|
case ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING: {
|
||||||
|
// existing request is about to be cancelled. If we want to
|
||||||
|
// resend, then change the state so that it resends after
|
||||||
|
// cancelling. Otherwise, just cancel the cancellation.
|
||||||
|
const state = resend ?
|
||||||
|
ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING_AND_WILL_RESEND :
|
||||||
|
ROOM_KEY_REQUEST_STATES.SENT;
|
||||||
|
await this._cryptoStore.updateOutgoingRoomKeyRequest(
|
||||||
|
req.requestId, ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING, {
|
||||||
|
state,
|
||||||
|
cancellationTxnId: this._baseApis.makeTxnId(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ROOM_KEY_REQUEST_STATES.SENT: {
|
||||||
|
// a request has already been sent. If we don't want to
|
||||||
|
// resend, then do nothing. If we do want to, then cancel the
|
||||||
|
// existing request and send a new one.
|
||||||
|
if (resend) {
|
||||||
|
const state =
|
||||||
|
ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING_AND_WILLRESEND;
|
||||||
|
const updatedReq =
|
||||||
|
await this._cryptoStore.updateOutgoingRoomKeyRequest(
|
||||||
|
req.requestId, ROOM_KEY_REQUEST_STATES.SENT, {
|
||||||
|
state,
|
||||||
|
cancellationTxnId: this._baseApis.makeTxnId(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (!updatedReq) {
|
||||||
|
// updateOutgoingRoomKeyRequest couldn't find the request
|
||||||
|
// in state ROOM_KEY_REQUEST_STATES.SENT, so we must have
|
||||||
|
// raced with another tab to mark the request cancelled.
|
||||||
|
// Try again, to make sure the request is resent.
|
||||||
|
return await this.sendRoomKeyRequest(
|
||||||
|
requestBody, recipients, resend,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't want to wait for the timer, so we send it
|
||||||
|
// immediately. (We might actually end up racing with the timer,
|
||||||
|
// but that's ok: even if we make the request twice, we'll do it
|
||||||
|
// with the same transaction_id, so only one message will get
|
||||||
|
// sent).
|
||||||
|
//
|
||||||
|
// (We also don't want to wait for the response from the server
|
||||||
|
// here, as it will slow down processing of received keys if we
|
||||||
|
// do.)
|
||||||
|
try {
|
||||||
|
await this._sendOutgoingRoomKeyRequestCancellation(
|
||||||
|
updatedReq,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(
|
||||||
|
"Error sending room key request cancellation;"
|
||||||
|
+ " will retry later.", e,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// The request has transitioned from
|
||||||
|
// CANCELLATION_PENDING_AND_WILL_RESEND to UNSENT. We
|
||||||
|
// still need to resend the request which is now UNSENT, so
|
||||||
|
// start the timer if it isn't already started.
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new Error('unhandled state: ' + req.state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// some of the branches require the timer to be started. Just start it
|
||||||
|
// all the time, because it doesn't hurt to start it.
|
||||||
|
this._startTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancel room key requests, if any match the given requestBody
|
* Cancel room key requests, if any match the given requestBody
|
||||||
*
|
*
|
||||||
* @param {module:crypto~RoomKeyRequestBody} requestBody
|
* @param {module:crypto~RoomKeyRequestBody} requestBody
|
||||||
* @param {boolean} andResend if true, transition to UNSENT instead of
|
|
||||||
* deleting after sending cancellation.
|
|
||||||
*
|
*
|
||||||
* @returns {Promise} resolves when the request has been updated in our
|
* @returns {Promise} resolves when the request has been updated in our
|
||||||
* pending list.
|
* pending list.
|
||||||
*/
|
*/
|
||||||
cancelRoomKeyRequest(requestBody, andResend=false) {
|
cancelRoomKeyRequest(requestBody) {
|
||||||
return this._cryptoStore.getOutgoingRoomKeyRequest(
|
return this._cryptoStore.getOutgoingRoomKeyRequest(
|
||||||
requestBody,
|
requestBody,
|
||||||
).then((req) => {
|
).then((req) => {
|
||||||
@@ -183,15 +263,10 @@ export default class OutgoingRoomKeyRequestManager {
|
|||||||
);
|
);
|
||||||
|
|
||||||
case ROOM_KEY_REQUEST_STATES.SENT: {
|
case ROOM_KEY_REQUEST_STATES.SENT: {
|
||||||
// If `andResend` is set, transition to UNSENT once the
|
|
||||||
// cancellation has successfully been sent.
|
|
||||||
const state = andResend ?
|
|
||||||
ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING_AND_WILL_RESEND :
|
|
||||||
ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING;
|
|
||||||
// send a cancellation.
|
// send a cancellation.
|
||||||
return this._cryptoStore.updateOutgoingRoomKeyRequest(
|
return this._cryptoStore.updateOutgoingRoomKeyRequest(
|
||||||
req.requestId, ROOM_KEY_REQUEST_STATES.SENT, {
|
req.requestId, ROOM_KEY_REQUEST_STATES.SENT, {
|
||||||
state,
|
state: ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING,
|
||||||
cancellationTxnId: this._baseApis.makeTxnId(),
|
cancellationTxnId: this._baseApis.makeTxnId(),
|
||||||
},
|
},
|
||||||
).then((updatedReq) => {
|
).then((updatedReq) => {
|
||||||
@@ -221,20 +296,12 @@ export default class OutgoingRoomKeyRequestManager {
|
|||||||
// do.)
|
// do.)
|
||||||
this._sendOutgoingRoomKeyRequestCancellation(
|
this._sendOutgoingRoomKeyRequestCancellation(
|
||||||
updatedReq,
|
updatedReq,
|
||||||
andResend,
|
|
||||||
).catch((e) => {
|
).catch((e) => {
|
||||||
logger.error(
|
logger.error(
|
||||||
"Error sending room key request cancellation;"
|
"Error sending room key request cancellation;"
|
||||||
+ " will retry later.", e,
|
+ " will retry later.", e,
|
||||||
);
|
);
|
||||||
this._startTimer();
|
this._startTimer();
|
||||||
}).then(() => {
|
|
||||||
if (!andResend) return;
|
|
||||||
// The request has transitioned from
|
|
||||||
// CANCELLATION_PENDING_AND_WILL_RESEND to UNSENT. We
|
|
||||||
// still need to resend the request which is now UNSENT, so
|
|
||||||
// start the timer if it isn't already started.
|
|
||||||
this._startTimer();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -815,19 +815,9 @@ MegolmDecryption.prototype.decryptEvent = async function(event) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
MegolmDecryption.prototype._requestKeysForEvent = function(event) {
|
MegolmDecryption.prototype._requestKeysForEvent = function(event) {
|
||||||
const sender = event.getSender();
|
|
||||||
const wireContent = event.getWireContent();
|
const wireContent = event.getWireContent();
|
||||||
|
|
||||||
// send the request to all of our own devices, and the
|
const recipients = event.getKeyRequestRecipients(this._userId);
|
||||||
// original sending device if it wasn't us.
|
|
||||||
const recipients = [{
|
|
||||||
userId: this._userId, deviceId: '*',
|
|
||||||
}];
|
|
||||||
if (sender != this._userId) {
|
|
||||||
recipients.push({
|
|
||||||
userId: sender, deviceId: wireContent.device_id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this._crypto.requestRoomKey({
|
this._crypto.requestRoomKey({
|
||||||
room_id: event.getRoomId(),
|
room_id: event.getRoomId(),
|
||||||
|
|||||||
@@ -1426,10 +1426,14 @@ Crypto.prototype.handleDeviceListChanges = async function(syncData, syncDeviceLi
|
|||||||
*
|
*
|
||||||
* @param {module:crypto~RoomKeyRequestBody} requestBody
|
* @param {module:crypto~RoomKeyRequestBody} requestBody
|
||||||
* @param {Array<{userId: string, deviceId: string}>} recipients
|
* @param {Array<{userId: string, deviceId: string}>} recipients
|
||||||
|
* @param {boolean} resend whether to resend the key request if there is
|
||||||
|
* already one
|
||||||
|
*
|
||||||
|
* @return {Promise} a promise that resolves when the key request is queued
|
||||||
*/
|
*/
|
||||||
Crypto.prototype.requestRoomKey = function(requestBody, recipients) {
|
Crypto.prototype.requestRoomKey = function(requestBody, recipients, resend=false) {
|
||||||
this._outgoingRoomKeyRequestManager.sendRoomKeyRequest(
|
return this._outgoingRoomKeyRequestManager.sendRoomKeyRequest(
|
||||||
requestBody, recipients,
|
requestBody, recipients, resend,
|
||||||
).catch((e) => {
|
).catch((e) => {
|
||||||
// this normally means we couldn't talk to the store
|
// this normally means we couldn't talk to the store
|
||||||
logger.error(
|
logger.error(
|
||||||
@@ -1443,11 +1447,9 @@ Crypto.prototype.requestRoomKey = function(requestBody, recipients) {
|
|||||||
*
|
*
|
||||||
* @param {module:crypto~RoomKeyRequestBody} requestBody
|
* @param {module:crypto~RoomKeyRequestBody} requestBody
|
||||||
* parameters to match for cancellation
|
* parameters to match for cancellation
|
||||||
* @param {boolean} andResend
|
|
||||||
* if true, resend the key request after cancelling.
|
|
||||||
*/
|
*/
|
||||||
Crypto.prototype.cancelRoomKeyRequest = function(requestBody, andResend) {
|
Crypto.prototype.cancelRoomKeyRequest = function(requestBody) {
|
||||||
this._outgoingRoomKeyRequestManager.cancelRoomKeyRequest(requestBody, andResend)
|
this._outgoingRoomKeyRequestManager.cancelRoomKeyRequest(requestBody)
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
logger.warn("Error clearing pending room key requests", e);
|
logger.warn("Error clearing pending room key requests", e);
|
||||||
}).done();
|
}).done();
|
||||||
@@ -1984,7 +1986,7 @@ Crypto.prototype._onToDeviceBadEncrypted = async function(event) {
|
|||||||
sender, device.deviceId,
|
sender, device.deviceId,
|
||||||
);
|
);
|
||||||
for (const keyReq of requestsToResend) {
|
for (const keyReq of requestsToResend) {
|
||||||
this.cancelRoomKeyRequest(keyReq.requestBody, true);
|
this.requestRoomKey(keyReq.requestBody, keyReq.recipients, true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -382,15 +382,39 @@ utils.extend(module.exports.MatrixEvent.prototype, {
|
|||||||
* Cancel any room key request for this event and resend another.
|
* Cancel any room key request for this event and resend another.
|
||||||
*
|
*
|
||||||
* @param {module:crypto} crypto crypto module
|
* @param {module:crypto} crypto crypto module
|
||||||
|
* @param {string} userId the user who received this event
|
||||||
*/
|
*/
|
||||||
cancelAndResendKeyRequest: function(crypto) {
|
cancelAndResendKeyRequest: function(crypto, userId) {
|
||||||
const wireContent = this.getWireContent();
|
const wireContent = this.getWireContent();
|
||||||
crypto.cancelRoomKeyRequest({
|
return crypto.requestRoomKey({
|
||||||
algorithm: wireContent.algorithm,
|
algorithm: wireContent.algorithm,
|
||||||
room_id: this.getRoomId(),
|
room_id: this.getRoomId(),
|
||||||
session_id: wireContent.session_id,
|
session_id: wireContent.session_id,
|
||||||
sender_key: wireContent.sender_key,
|
sender_key: wireContent.sender_key,
|
||||||
}, true);
|
}, this.getKeyRequestRecipients(userId), true);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the recipients for keyshare requests.
|
||||||
|
*
|
||||||
|
* @param {string} userId the user who received this event.
|
||||||
|
*
|
||||||
|
* @returns {Array} array of recipients
|
||||||
|
*/
|
||||||
|
getKeyRequestRecipients: function(userId) {
|
||||||
|
// send the request to all of our own devices, and the
|
||||||
|
// original sending device if it wasn't us.
|
||||||
|
const wireContent = this.getWireContent();
|
||||||
|
const recipients = [{
|
||||||
|
userId, deviceId: '*',
|
||||||
|
}];
|
||||||
|
const sender = this.getSender();
|
||||||
|
if (sender !== userId) {
|
||||||
|
recipients.push({
|
||||||
|
userId: sender, deviceId: wireContent.device_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return recipients;
|
||||||
},
|
},
|
||||||
|
|
||||||
_decryptionLoop: async function(crypto) {
|
_decryptionLoop: async function(crypto) {
|
||||||
|
|||||||
Reference in New Issue
Block a user