Refactor session reset handling
This commit is contained in:
parent
5fc14d2a7e
commit
8ffb1a0a10
|
@ -63,7 +63,7 @@ module.exports = {
|
|||
// high value as a buffer to let Prettier control the line length:
|
||||
code: 999,
|
||||
// We still want to limit comments as before:
|
||||
comments: 90,
|
||||
comments: 150,
|
||||
ignoreUrls: true,
|
||||
ignoreRegExpLiterals: true,
|
||||
},
|
||||
|
|
|
@ -475,7 +475,6 @@ SecretSessionCipher.prototype = {
|
|||
|
||||
// private byte[] decrypt(UnidentifiedSenderMessageContent message)
|
||||
_decryptWithUnidentifiedSenderMessage(message) {
|
||||
const { SessionCipher } = this;
|
||||
const signalProtocolStore = this.storage;
|
||||
|
||||
const sender = new libsignal.SignalProtocolAddress(
|
||||
|
@ -485,12 +484,12 @@ SecretSessionCipher.prototype = {
|
|||
|
||||
switch (message.type) {
|
||||
case CiphertextMessage.WHISPER_TYPE:
|
||||
return new SessionCipher(
|
||||
return new libloki.crypto.LokiSessionCipher(
|
||||
signalProtocolStore,
|
||||
sender
|
||||
).decryptWhisperMessage(message.content);
|
||||
case CiphertextMessage.PREKEY_TYPE:
|
||||
return new SessionCipher(
|
||||
return new libloki.crypto.LokiSessionCipher(
|
||||
signalProtocolStore,
|
||||
sender
|
||||
).decryptPreKeyWhisperMessage(message.content);
|
||||
|
|
|
@ -324,6 +324,146 @@
|
|||
GRANT: 2,
|
||||
});
|
||||
|
||||
/**
|
||||
* A wrapper around Signal's SessionCipher.
|
||||
* This handles specific session reset logic that we need.
|
||||
*/
|
||||
class LokiSessionCipher {
|
||||
constructor(storage, address) {
|
||||
this.storage = storage;
|
||||
this.address = address;
|
||||
this.sessionCipher = new libsignal.SessionCipher(storage, address);
|
||||
}
|
||||
|
||||
async decryptWhisperMessage(buffer, encoding) {
|
||||
// Capture active session
|
||||
const activeSessionBaseKey = await this._getCurrentSessionBaseKey();
|
||||
|
||||
const promise = this.sessionCipher.decryptWhisperMessage(
|
||||
buffer,
|
||||
encoding
|
||||
);
|
||||
|
||||
// Handle session reset
|
||||
// eslint-disable-next-line more/no-then
|
||||
promise.then(() => {
|
||||
this._handleSessionResetIfNeeded(activeSessionBaseKey);
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
async decryptPreKeyWhisperMessage(buffer, encoding) {
|
||||
// Capture active session
|
||||
const activeSessionBaseKey = await this._getCurrentSessionBaseKey();
|
||||
|
||||
if (!activeSessionBaseKey) {
|
||||
const wrapped = dcodeIO.ByteBuffer.wrap(buffer);
|
||||
await window.libloki.storage.verifyFriendRequestAcceptPreKey(
|
||||
this.address.getName(),
|
||||
wrapped
|
||||
);
|
||||
}
|
||||
|
||||
const promise = this.sessionCipher.decryptPreKeyWhisperMessage(
|
||||
buffer,
|
||||
encoding
|
||||
);
|
||||
|
||||
// Handle session reset
|
||||
// eslint-disable-next-line more/no-then
|
||||
promise.then(() => {
|
||||
this._handleSessionResetIfNeeded(activeSessionBaseKey);
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
async _handleSessionResetIfNeeded(previousSessionBaseKey) {
|
||||
if (!previousSessionBaseKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
let conversation;
|
||||
try {
|
||||
conversation = await window.ConversationController.getOrCreateAndWait(
|
||||
this.address.getName(),
|
||||
'private'
|
||||
);
|
||||
} catch (e) {
|
||||
window.log.info('Error getting conversation: ', this.address.getName());
|
||||
return;
|
||||
}
|
||||
|
||||
if (conversation.isSessionResetOngoing()) {
|
||||
const currentSessionBaseKey = await this._getCurrentSessionBaseKey();
|
||||
if (currentSessionBaseKey !== previousSessionBaseKey) {
|
||||
if (conversation.isSessionResetReceived()) {
|
||||
// The other user used an old session to contact us; wait for them to switch to a new one.
|
||||
await this._restoreSession(previousSessionBaseKey);
|
||||
} else {
|
||||
// Our session reset was successful; we initiated one and got a new session back from the other user.
|
||||
await this._deleteAllSessionExcept(currentSessionBaseKey);
|
||||
await conversation.onNewSessionAdopted();
|
||||
}
|
||||
} else if (conversation.isSessionResetReceived()) {
|
||||
// Our session reset was successful; we received a message with the same session from the other user.
|
||||
await this._deleteAllSessionExcept(previousSessionBaseKey);
|
||||
await conversation.onNewSessionAdopted();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async _getCurrentSessionBaseKey() {
|
||||
const record = await this.sessionCipher.getRecord(
|
||||
this.address.toString()
|
||||
);
|
||||
if (!record) {
|
||||
return null;
|
||||
}
|
||||
const openSession = record.getOpenSession();
|
||||
if (!openSession) {
|
||||
return null;
|
||||
}
|
||||
const { baseKey } = openSession.indexInfo;
|
||||
return baseKey;
|
||||
}
|
||||
|
||||
async _restoreSession(sessionBaseKey) {
|
||||
const record = await this.sessionCipher.getRecord(
|
||||
this.address.toString()
|
||||
);
|
||||
if (!record) {
|
||||
return;
|
||||
}
|
||||
record.archiveCurrentState();
|
||||
|
||||
const sessionToRestore = record.sessions[sessionBaseKey];
|
||||
record.promoteState(sessionToRestore);
|
||||
record.updateSessionState(sessionToRestore);
|
||||
await this.storage.storeSession(
|
||||
this.address.toString(),
|
||||
record.serialize()
|
||||
);
|
||||
}
|
||||
|
||||
async _deleteAllSessionExcept(sessionBaseKey) {
|
||||
const record = await this.sessionCipher.getRecord(
|
||||
this.address.toString()
|
||||
);
|
||||
if (!record) {
|
||||
return;
|
||||
}
|
||||
const sessionToKeep = record.sessions[sessionBaseKey];
|
||||
record.sessions = {};
|
||||
record.updateSessionState(sessionToKeep);
|
||||
await this.storage.storeSession(
|
||||
this.address.toString(),
|
||||
record.serialize()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
window.libloki.crypto = {
|
||||
DHEncrypt,
|
||||
DHDecrypt,
|
||||
|
@ -336,6 +476,7 @@
|
|||
verifyAuthorisation,
|
||||
validateAuthorisation,
|
||||
PairingType,
|
||||
LokiSessionCipher,
|
||||
// for testing
|
||||
_LokiSnodeChannel: LokiSnodeChannel,
|
||||
_decodeSnodeAddressToPubKey: decodeSnodeAddressToPubKey,
|
||||
|
|
|
@ -667,58 +667,29 @@ MessageReceiver.prototype.extend({
|
|||
async decrypt(envelope, ciphertext) {
|
||||
let promise;
|
||||
|
||||
// We don't have source at this point yet (with sealed sender)
|
||||
// This needs a massive cleanup!
|
||||
const address = new libsignal.SignalProtocolAddress(
|
||||
envelope.source,
|
||||
envelope.sourceDevice
|
||||
);
|
||||
|
||||
const ourNumber = textsecure.storage.user.getNumber();
|
||||
const number = address.toString().split('.')[0];
|
||||
const options = {};
|
||||
|
||||
// No limit on message keys if we're communicating with our other devices
|
||||
if (ourNumber === number) {
|
||||
options.messageKeysLimit = false;
|
||||
}
|
||||
|
||||
// Will become obsolete
|
||||
const sessionCipher = new libsignal.SessionCipher(
|
||||
textsecure.storage.protocol,
|
||||
address,
|
||||
options
|
||||
);
|
||||
|
||||
const me = {
|
||||
number: ourNumber,
|
||||
deviceId: parseInt(textsecure.storage.user.getDeviceId(), 10),
|
||||
};
|
||||
|
||||
// Will become obsolete
|
||||
const getCurrentSessionBaseKey = async () => {
|
||||
const record = await sessionCipher.getRecord(address.toString());
|
||||
if (!record) {
|
||||
return null;
|
||||
}
|
||||
const openSession = record.getOpenSession();
|
||||
if (!openSession) {
|
||||
return null;
|
||||
}
|
||||
const { baseKey } = openSession.indexInfo;
|
||||
return baseKey;
|
||||
};
|
||||
// Envelope.source will be null on UNIDENTIFIED_SENDER
|
||||
// Don't use it there!
|
||||
const address = new libsignal.SignalProtocolAddress(
|
||||
envelope.source,
|
||||
envelope.sourceDevice
|
||||
);
|
||||
|
||||
// Will become obsolete
|
||||
const captureActiveSession = async () => {
|
||||
this.activeSessionBaseKey = await getCurrentSessionBaseKey(sessionCipher);
|
||||
};
|
||||
const lokiSessionCipher = new libloki.crypto.LokiSessionCipher(
|
||||
textsecure.storage.protocol,
|
||||
address
|
||||
);
|
||||
|
||||
switch (envelope.type) {
|
||||
case textsecure.protobuf.Envelope.Type.CIPHERTEXT:
|
||||
window.log.info('message from', this.getEnvelopeId(envelope));
|
||||
promise = captureActiveSession()
|
||||
.then(() => sessionCipher.decryptWhisperMessage(ciphertext))
|
||||
promise = lokiSessionCipher
|
||||
.decryptWhisperMessage(ciphertext)
|
||||
.then(this.unpad);
|
||||
break;
|
||||
case textsecure.protobuf.Envelope.Type.FRIEND_REQUEST: {
|
||||
|
@ -735,25 +706,11 @@ MessageReceiver.prototype.extend({
|
|||
}
|
||||
case textsecure.protobuf.Envelope.Type.PREKEY_BUNDLE:
|
||||
window.log.info('prekey message from', this.getEnvelopeId(envelope));
|
||||
promise = captureActiveSession(sessionCipher).then(async () => {
|
||||
if (!this.activeSessionBaseKey) {
|
||||
try {
|
||||
const buffer = dcodeIO.ByteBuffer.wrap(ciphertext);
|
||||
await window.libloki.storage.verifyFriendRequestAcceptPreKey(
|
||||
envelope.source,
|
||||
buffer
|
||||
);
|
||||
} catch (e) {
|
||||
await this.removeFromCache(envelope);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return this.decryptPreKeyWhisperMessage(
|
||||
ciphertext,
|
||||
sessionCipher,
|
||||
address
|
||||
);
|
||||
});
|
||||
promise = this.decryptPreKeyWhisperMessage(
|
||||
ciphertext,
|
||||
lokiSessionCipher,
|
||||
address
|
||||
);
|
||||
break;
|
||||
case textsecure.protobuf.Envelope.Type.UNIDENTIFIED_SENDER: {
|
||||
window.log.info('received unidentified sender message');
|
||||
|
@ -856,72 +813,6 @@ MessageReceiver.prototype.extend({
|
|||
window.log.info('Error getting conversation: ', envelope.source);
|
||||
}
|
||||
|
||||
// lint hates anything after // (so /// is no good)
|
||||
// *** BEGIN: session reset ***
|
||||
|
||||
// we have address in scope from parent scope
|
||||
// seems to be the same input parameters
|
||||
// going to comment out due to lint complaints
|
||||
/*
|
||||
const address = new libsignal.SignalProtocolAddress(
|
||||
envelope.source,
|
||||
envelope.sourceDevice
|
||||
);
|
||||
*/
|
||||
|
||||
const restoreActiveSession = async () => {
|
||||
const record = await sessionCipher.getRecord(address.toString());
|
||||
if (!record) {
|
||||
return;
|
||||
}
|
||||
record.archiveCurrentState();
|
||||
|
||||
// NOTE: activeSessionBaseKey will be undefined here...
|
||||
const sessionToRestore = record.sessions[this.activeSessionBaseKey];
|
||||
record.promoteState(sessionToRestore);
|
||||
record.updateSessionState(sessionToRestore);
|
||||
await textsecure.storage.protocol.storeSession(
|
||||
address.toString(),
|
||||
record.serialize()
|
||||
);
|
||||
};
|
||||
const deleteAllSessionExcept = async sessionBaseKey => {
|
||||
const record = await sessionCipher.getRecord(address.toString());
|
||||
if (!record) {
|
||||
return;
|
||||
}
|
||||
const sessionToKeep = record.sessions[sessionBaseKey];
|
||||
record.sessions = {};
|
||||
record.updateSessionState(sessionToKeep);
|
||||
await textsecure.storage.protocol.storeSession(
|
||||
address.toString(),
|
||||
record.serialize()
|
||||
);
|
||||
};
|
||||
|
||||
if (conversation.isSessionResetOngoing()) {
|
||||
const currentSessionBaseKey = await getCurrentSessionBaseKey(
|
||||
sessionCipher
|
||||
);
|
||||
if (
|
||||
this.activeSessionBaseKey &&
|
||||
currentSessionBaseKey !== this.activeSessionBaseKey
|
||||
) {
|
||||
if (conversation.isSessionResetReceived()) {
|
||||
await restoreActiveSession();
|
||||
} else {
|
||||
await deleteAllSessionExcept(currentSessionBaseKey);
|
||||
await conversation.onNewSessionAdopted();
|
||||
}
|
||||
} else if (conversation.isSessionResetReceived()) {
|
||||
await deleteAllSessionExcept(this.activeSessionBaseKey);
|
||||
await conversation.onNewSessionAdopted();
|
||||
}
|
||||
}
|
||||
|
||||
// lint hates anything after // (so /// is no good)
|
||||
// *** END ***
|
||||
|
||||
// Type here can actually be UNIDENTIFIED_SENDER even if
|
||||
// the underlying message is FRIEND_REQUEST
|
||||
if (
|
||||
|
|
Loading…
Reference in New Issue