From fbc6554d6f171f8b7aec9a452309fae97debc880 Mon Sep 17 00:00:00 2001 From: lilia Date: Thu, 28 Jul 2016 17:22:23 -0700 Subject: [PATCH] Decrypt against previous sessions In congruence with the java implementation, attempt to decrypt against previous session states if decryption fails using the open session. --- dist/libsignal-protocol.js | 56 +++++++++++++++++++++++++++++++------- src/SessionCipher.js | 35 +++++++++++++++++------- src/SessionRecord.js | 21 ++++++++++++++ 3 files changed, 92 insertions(+), 20 deletions(-) diff --git a/dist/libsignal-protocol.js b/dist/libsignal-protocol.js index 0456fc2..414ad59 100644 --- a/dist/libsignal-protocol.js +++ b/dist/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), @@ -36110,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() { @@ -36118,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)); @@ -36184,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'); diff --git a/src/SessionCipher.js b/src/SessionCipher.js index d10499f..71cb43e 100644 --- a/src/SessionCipher.js +++ b/src/SessionCipher.js @@ -101,6 +101,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() { @@ -109,15 +125,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)); @@ -175,7 +190,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/src/SessionRecord.js b/src/SessionRecord.js index 1cc5109..eec9d86 100644 --- a/src/SessionRecord.js +++ b/src/SessionRecord.js @@ -184,6 +184,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) { @@ -195,6 +215,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),