Prompting the user to reset session on invalid ciphertext

This commit is contained in:
Maxim Shishmarev 2019-11-28 13:07:05 +11:00
parent 5a4ea93594
commit 81bfa90943
12 changed files with 176 additions and 5 deletions

View File

@ -2241,5 +2241,18 @@
},
"noFriendsToAdd": {
"message": "no friends to add"
},
"couldNotDecryptMessage": {
"message": "Couldn't decrypt a message"
},
"confirmSessionRestore": {
"message":
"Would you like to start a new session with $pubkey$? Only do so if you know this pubkey.",
"placeholders": {
"pubkey": {
"content": "$1",
"example": ""
}
}
}
}

View File

@ -819,6 +819,7 @@
<script type='text/javascript' src='js/views/device_pairing_dialog_view.js'></script>
<script type='text/javascript' src='js/views/device_pairing_words_dialog_view.js'></script>
<script type='text/javascript' src='js/views/create_group_dialog_view.js'></script>
<script type='text/javascript' src='js/views/confirm_session_reset_view.js'></script>
<script type='text/javascript' src='js/views/edit_profile_dialog_view.js'></script>
<script type='text/javascript' src='js/views/invite_friends_dialog_view.js'></script>

View File

@ -998,6 +998,12 @@
}
});
Whisper.events.on('showSessionRestoreConfirmation', options => {
if (appView) {
appView.showSessionRestoreConfirmation(options);
}
});
Whisper.events.on('showNicknameDialog', options => {
if (appView) {
appView.showNicknameDialog(options);
@ -1889,6 +1895,48 @@
}
async function onError(ev) {
const noSession =
ev.error &&
ev.error.message &&
ev.error.message.indexOf('No record for device') === 0;
const pubkey = ev.proto.source;
if (noSession) {
const convo = await ConversationController.getOrCreateAndWait(
pubkey,
'private'
);
if (!convo.get('sessionRestoreSeen')) {
convo.set({ sessionRestoreSeen: true });
await window.Signal.Data.updateConversation(
convo.id,
convo.attributes,
{ Conversation: Whisper.Conversation }
);
window.Whisper.events.trigger('showSessionRestoreConfirmation', {
pubkey,
onOk: async () => {
convo.sendMessage('', null, null, null, null, {
sessionRestoration: true,
});
},
});
} else {
window.log.verbose(
`Already seen session restore for pubkey: ${pubkey}`
);
if (ev.confirm) {
ev.confirm();
}
}
// We don't want to display any failed messages in the conversation:
return;
}
const { error } = ev;
window.log.error('background onError:', Errors.toLogFormat(error));

View File

@ -1423,7 +1423,8 @@
attachments,
quote,
preview,
groupInvitation = null
groupInvitation = null,
otherOptions = {}
) {
this.clearTypingTimers();
@ -1514,9 +1515,17 @@
messageWithSchema.source = textsecure.storage.user.getNumber();
messageWithSchema.sourceDevice = 1;
}
let sessionRestoration = false;
if (otherOptions) {
sessionRestoration = otherOptions.sessionRestoration || false;
}
const attributes = {
...messageWithSchema,
groupInvitation,
sessionRestoration,
id: window.getGuid(),
};
@ -1597,6 +1606,7 @@
}
options.groupInvitation = groupInvitation;
options.sessionRestoration = sessionRestoration;
const groupNumbers = this.getRecipients();

View File

@ -102,6 +102,8 @@
this.propsForResetSessionNotification = this.getPropsForResetSessionNotification();
} else if (this.isGroupUpdate()) {
this.propsForGroupNotification = this.getPropsForGroupNotification();
} else if (this.isSessionRestoration()) {
// do nothing
} else if (this.isFriendRequest()) {
this.propsForFriendRequest = this.getPropsForFriendRequest();
} else if (this.isGroupInvitation()) {
@ -270,6 +272,13 @@
isGroupInvitation() {
return !!this.get('groupInvitation');
},
isSessionRestoration() {
const flag = textsecure.protobuf.DataMessage.Flags.SESSION_RESTORE;
// eslint-disable-next-line no-bitwise
const sessionRestoreFlag = !!(this.get('flags') & flag);
return !!this.get('sessionRestoration') || sessionRestoreFlag;
},
getNotificationText() {
const description = this.getDescription();
if (description) {
@ -390,6 +399,16 @@
}
const conversation = await this.getSourceDeviceConversation();
// If we somehow received an old friend request (e.g. after having restored
// from seed, we won't be able to accept it, we should initiate our own
// friend request to reset the session:
if (conversation.get('sessionRestoreSeen')) {
conversation.sendMessage('', null, null, null, null, {
sessionRestoration: true,
});
return;
}
this.set({ friendStatus: 'accepted' });
await window.Signal.Data.saveMessage(this.attributes, {
Message: Whisper.Message,
@ -1922,6 +1941,15 @@
initialMessage.flags ===
textsecure.protobuf.DataMessage.Flags.BACKGROUND_FRIEND_REQUEST;
if (
// eslint-disable-next-line no-bitwise
initialMessage.flags &
textsecure.protobuf.DataMessage.Flags.SESSION_RESTORE
) {
// Show that the session reset is "in progress" even though we had a valid session
this.set({ endSessionType: 'ongoing' });
}
if (message.isFriendRequest() && backgroundFrReq) {
// Check if the contact is a member in one of our private groups:
const groupMember = window

View File

@ -250,6 +250,10 @@
const dialog = new Whisper.UpdateGroupDialogView(groupConvo);
this.el.append(dialog.el);
},
showSessionRestoreConfirmation(options) {
const dialog = new Whisper.ConfirmSessionResetView(options);
this.el.append(dialog.el);
},
showLeaveGroupDialog(groupConvo) {
const dialog = new Whisper.LeaveGroupDialogView(groupConvo);
this.el.append(dialog.el);

View File

@ -0,0 +1,50 @@
/* global Whisper, i18n */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.ConfirmSessionResetView = Whisper.View.extend({
className: 'loki-dialog modal',
initialize({ pubkey, onOk }) {
this.title = i18n('couldNotDecryptMessage');
this.onOk = onOk;
this.messageText = i18n('confirmSessionRestore', pubkey);
this.okText = i18n('yes');
this.cancelText = i18n('cancel');
this.close = this.close.bind(this);
this.confirm = this.confirm.bind(this);
this.$el.focus();
this.render();
},
render() {
this.dialogView = new Whisper.ReactWrapperView({
className: 'leave-group-dialog',
Component: window.Signal.Components.ConfirmDialog,
props: {
titleText: this.title,
messageText: this.messageText,
okText: this.okText,
cancelText: this.cancelText,
onConfirm: this.confirm,
onClose: this.close,
},
});
this.$el.append(this.dialogView.el);
return this;
},
async confirm() {
this.onOk();
this.close();
},
close() {
this.remove();
},
});
})();

View File

@ -69,6 +69,11 @@
Component: Components.GroupNotification,
props: this.model.propsForGroupNotification,
};
} else if (this.model.isSessionRestoration()) {
return {
Component: Components.ResetSessionNotification,
props: this.model.getPropsForResetSessionNotification(),
};
} else if (this.model.propsForFriendRequest) {
return {
Component: Components.FriendRequest,

View File

@ -821,7 +821,6 @@ MessageReceiver.prototype.extend({
} else {
handleSessionReset = async result => result;
}
switch (envelope.type) {
case textsecure.protobuf.Envelope.Type.CIPHERTEXT:
window.log.info('message from', this.getEnvelopeId(envelope));
@ -971,6 +970,9 @@ MessageReceiver.prototype.extend({
.catch(error => {
let errorToThrow = error;
const noSession =
error && error.message.indexOf('No record for device') === 0;
if (error && error.message === 'Unknown identity key') {
// create an error that the UI will pick up and ask the
// user if they want to re-negotiate
@ -980,8 +982,8 @@ MessageReceiver.prototype.extend({
buffer.toArrayBuffer(),
error.identityKey
);
} else {
// re-throw
} else if (!noSession) {
// We want to handle "no-session" error, not re-throw it
throw error;
}
const ev = new Event('error');
@ -1850,6 +1852,8 @@ MessageReceiver.prototype.extend({
decrypted.attachments = [];
} else if (decrypted.flags & FLAGS.BACKGROUND_FRIEND_REQUEST) {
// do nothing
} else if (decrypted.flags & FLAGS.SESSION_RESTORE) {
// do nothing
} else if (decrypted.flags & FLAGS.UNPAIRING_REQUEST) {
// do nothing
} else if (decrypted.flags !== 0) {

View File

@ -28,6 +28,7 @@ function Message(options) {
this.profileKey = options.profileKey;
this.profile = options.profile;
this.groupInvitation = options.groupInvitation;
this.sessionRestoration = options.sessionRestoration || false;
if (!(this.recipients instanceof Array)) {
throw new Error('Invalid recipient list');
@ -171,6 +172,10 @@ Message.prototype = {
);
}
if (this.sessionRestoration) {
proto.flags = textsecure.protobuf.DataMessage.Flags.SESSION_RESTORE;
}
this.dataMessage = proto;
return proto;
},
@ -952,7 +957,7 @@ MessageSender.prototype = {
? textsecure.protobuf.DataMessage.Flags.BACKGROUND_FRIEND_REQUEST
: undefined;
const { groupInvitation } = options;
const { groupInvitation, sessionRestoration } = options;
return this.sendMessage(
{
@ -968,6 +973,7 @@ MessageSender.prototype = {
profile,
flags,
groupInvitation,
sessionRestoration,
},
options
);

View File

@ -105,6 +105,7 @@ message DataMessage {
END_SESSION = 1;
EXPIRATION_TIMER_UPDATE = 2;
PROFILE_KEY_UPDATE = 4;
SESSION_RESTORE = 64;
UNPAIRING_REQUEST = 128;
BACKGROUND_FRIEND_REQUEST = 256;
}

View File

@ -576,6 +576,7 @@
<script type='text/javascript' src='../js/views/banner_view.js' data-cover></script>
<script type='text/javascript' src='../js/views/clear_data_view.js'></script>
<script type='text/javascript' src='../js/views/create_group_dialog_view.js'></script>
<script type='text/javascript' src='../js/views/confirm_session_reset_view.js'></script>
<script type='text/javascript' src='../js/views/invite_friends_dialog_view.js'></script>
<script type='text/javascript' src='../js/views/beta_release_disclaimer_view.js'></script>