Prompting the user to reset session on invalid ciphertext
This commit is contained in:
parent
5a4ea93594
commit
81bfa90943
|
@ -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": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
},
|
||||
});
|
||||
})();
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
Loading…
Reference in New Issue