diff --git a/js/libtextsecure.js b/js/libtextsecure.js index d3dc474c..a1beee97 100644 --- a/js/libtextsecure.js +++ b/js/libtextsecure.js @@ -35803,6 +35803,26 @@ Internal.SessionRecord = function() { throw new Error("Had open sessions on a record that had no registrationId set"); } }, + getSessions: function() { + // return an array of sessions ordered by time closed, + // followed by the open session + var list = []; + var openSession; + for (var k in this._sessions) { + if (this._sessions[k].indexInfo.closed === -1) { + openSession = this._sessions[k]; + } else { + list.push(this._sessions[k]); + } + } + list = list.sort(function(s1, s2) { + return s1.indexInfo.closed - s2.indexInfo.closed; + }); + if (openSession) { + list.push(openSession); + } + return list; + }, archiveCurrentState: function() { var open_session = this.getOpenSession(); if (open_session !== undefined) { @@ -35814,6 +35834,7 @@ Internal.SessionRecord = function() { if (session.indexInfo.closed > -1) { return; } + console.log('closing session', session.indexInfo.baseKey); // After this has run, we can still receive messages on ratchet chains which // were already open (unless we know we dont need them), @@ -35829,9 +35850,6 @@ Internal.SessionRecord = function() { }; } } - // Delete current root key and our ephemeral key pair to disallow ratchet stepping - delete session.currentRatchet.rootKey; - delete session.currentRatchet.ephemeralKeyPair; session.indexInfo.closed = Date.now(); this.removeOldChains(session); }, @@ -36237,6 +36255,22 @@ SessionCipher.prototype = { }); }.bind(this)); }, + decryptWithSessionList: function(buffer, sessionList, errors) { + // Iterate recursively through the list, attempting to decrypt + // using each one at a time. Stop and return the result if we get + // a valid result + if (sessionList.length === 0) { + return Promise.reject(errors[0]); + } + + var session = sessionList.pop(); + return this.doDecryptWhisperMessage(buffer, session).then(function(plaintext) { + return { plaintext: plaintext, session: session }; + }).catch(function(e) { + errors.push(e); + return this.decryptWithSessionList(buffer, sessionList, errors); + }.bind(this)); + }, decryptWhisperMessage: function(buffer, encoding) { buffer = dcodeIO.ByteBuffer.wrap(buffer, encoding).toArrayBuffer(); return Internal.SessionLock.queueJobForNumber(this.remoteAddress.toString(), function() { @@ -36245,15 +36279,14 @@ SessionCipher.prototype = { if (!record) { throw new Error("No record for device " + address); } - var messageProto = buffer.slice(1, buffer.byteLength - 8); - var message = Internal.protobuf.WhisperMessage.decode(messageProto); - var remoteEphemeralKey = message.ephemeralKey.toArrayBuffer(); - var session = record.getSessionByRemoteEphemeralKey(remoteEphemeralKey); - return this.doDecryptWhisperMessage(buffer, session).then(function(plaintext) { - record.updateSessionState(session); - return this.storage.storeSession(address, record.serialize()).then(function() { - return plaintext; - }); + var errors = []; + return this.decryptWithSessionList(buffer, record.getSessions(), errors).then(function(result) { + return this.getRecord(address).then(function(record) { + record.updateSessionState(result.session); + return this.storage.storeSession(address, record.serialize()).then(function() { + return result.plaintext; + }); + }.bind(this)); }.bind(this)); }.bind(this)); }.bind(this)); @@ -36311,7 +36344,7 @@ SessionCipher.prototype = { var remoteEphemeralKey = message.ephemeralKey.toArrayBuffer(); if (session === undefined) { - throw new Error("No session found to decrypt message from " + this.remoteAddress.toString()); + return Promise.reject(new Error("No session found to decrypt message from " + this.remoteAddress.toString())); } if (session.indexInfo.closed != -1) { console.log('decrypting message for closed session'); diff --git a/libtextsecure/libsignal-protocol.js b/libtextsecure/libsignal-protocol.js index 9daec74c..414ad594 100644 --- a/libtextsecure/libsignal-protocol.js +++ b/libtextsecure/libsignal-protocol.js @@ -35679,6 +35679,26 @@ Internal.SessionRecord = function() { throw new Error("Had open sessions on a record that had no registrationId set"); } }, + getSessions: function() { + // return an array of sessions ordered by time closed, + // followed by the open session + var list = []; + var openSession; + for (var k in this._sessions) { + if (this._sessions[k].indexInfo.closed === -1) { + openSession = this._sessions[k]; + } else { + list.push(this._sessions[k]); + } + } + list = list.sort(function(s1, s2) { + return s1.indexInfo.closed - s2.indexInfo.closed; + }); + if (openSession) { + list.push(openSession); + } + return list; + }, archiveCurrentState: function() { var open_session = this.getOpenSession(); if (open_session !== undefined) { @@ -35690,6 +35710,7 @@ Internal.SessionRecord = function() { if (session.indexInfo.closed > -1) { return; } + console.log('closing session', session.indexInfo.baseKey); // After this has run, we can still receive messages on ratchet chains which // were already open (unless we know we dont need them), @@ -35705,9 +35726,6 @@ Internal.SessionRecord = function() { }; } } - // Delete current root key and our ephemeral key pair to disallow ratchet stepping - delete session.currentRatchet.rootKey; - delete session.currentRatchet.ephemeralKeyPair; session.indexInfo.closed = Date.now(); this.removeOldChains(session); }, @@ -36113,6 +36131,22 @@ SessionCipher.prototype = { }); }.bind(this)); }, + decryptWithSessionList: function(buffer, sessionList, errors) { + // Iterate recursively through the list, attempting to decrypt + // using each one at a time. Stop and return the result if we get + // a valid result + if (sessionList.length === 0) { + return Promise.reject(errors[0]); + } + + var session = sessionList.pop(); + return this.doDecryptWhisperMessage(buffer, session).then(function(plaintext) { + return { plaintext: plaintext, session: session }; + }).catch(function(e) { + errors.push(e); + return this.decryptWithSessionList(buffer, sessionList, errors); + }.bind(this)); + }, decryptWhisperMessage: function(buffer, encoding) { buffer = dcodeIO.ByteBuffer.wrap(buffer, encoding).toArrayBuffer(); return Internal.SessionLock.queueJobForNumber(this.remoteAddress.toString(), function() { @@ -36121,15 +36155,14 @@ SessionCipher.prototype = { if (!record) { throw new Error("No record for device " + address); } - var messageProto = buffer.slice(1, buffer.byteLength - 8); - var message = Internal.protobuf.WhisperMessage.decode(messageProto); - var remoteEphemeralKey = message.ephemeralKey.toArrayBuffer(); - var session = record.getSessionByRemoteEphemeralKey(remoteEphemeralKey); - return this.doDecryptWhisperMessage(buffer, session).then(function(plaintext) { - record.updateSessionState(session); - return this.storage.storeSession(address, record.serialize()).then(function() { - return plaintext; - }); + var errors = []; + return this.decryptWithSessionList(buffer, record.getSessions(), errors).then(function(result) { + return this.getRecord(address).then(function(record) { + record.updateSessionState(result.session); + return this.storage.storeSession(address, record.serialize()).then(function() { + return result.plaintext; + }); + }.bind(this)); }.bind(this)); }.bind(this)); }.bind(this)); @@ -36187,7 +36220,7 @@ SessionCipher.prototype = { var remoteEphemeralKey = message.ephemeralKey.toArrayBuffer(); if (session === undefined) { - throw new Error("No session found to decrypt message from " + this.remoteAddress.toString()); + return Promise.reject(new Error("No session found to decrypt message from " + this.remoteAddress.toString())); } if (session.indexInfo.closed != -1) { console.log('decrypting message for closed session');