diff --git a/libtextsecure/libsignal-protocol.js b/libtextsecure/libsignal-protocol.js index 1fe5fd7338..513db98ca2 100644 --- a/libtextsecure/libsignal-protocol.js +++ b/libtextsecure/libsignal-protocol.js @@ -24690,6 +24690,25 @@ libsignal.SessionBuilder = function (storage, remoteAddress) { this.processV3 = builder.processV3.bind(builder); }; +function cleanOldMessageKeys(messageKeys) { + var limit = 2000; + var counters = Object.keys(messageKeys); + if (counters.length <= limit) { + return; + } + + console.log('cleaning old message keys', counters.length); + + // Sort counters in increasing order + var intCounters = counters + .map(string => parseInt(string, 10)) + .sort((a, b) => a - b); + + while (intCounters.length > limit) { + delete messageKeys[intCounters.shift()]; + } +} + function SessionCipher(storage, remoteAddress, options) { this.remoteAddress = remoteAddress; this.storage = storage; @@ -25028,8 +25047,13 @@ SessionCipher.prototype = { throw error; }); }, - fillMessageKeys: function(chain, counter) { + fillMessageKeys: function(chain, counter, hasChanged = false) { if (chain.chainKey.counter >= counter) { + // End of recursive iteration. Time to cleanup + if (hasChanged) { + cleanOldMessageKeys(chain.messageKeys); + } + return Promise.resolve(); // Already calculated } @@ -25060,7 +25084,7 @@ SessionCipher.prototype = { chain.messageKeys[chain.chainKey.counter + 1] = mac; chain.chainKey.key = key; chain.chainKey.counter += 1; - return this.fillMessageKeys(chain, counter); + return this.fillMessageKeys(chain, counter, true); }.bind(this)); }.bind(this)); }, @@ -25209,6 +25233,9 @@ libsignal.SessionCipher = function(storage, remoteAddress) { this.deleteAllSessionsForDevice = cipher.deleteAllSessionsForDevice.bind(cipher); }; +// Only for tests +libsignal.SessionCipher.cleanOldMessageKeys = cleanOldMessageKeys; + /* * jobQueue manages multiple queues indexed by device to serialize * session io ops on the database. diff --git a/libtextsecure/test/protocol_wrapper_test.js b/libtextsecure/test/protocol_wrapper_test.js index 138725c765..50d5092b54 100644 --- a/libtextsecure/test/protocol_wrapper_test.js +++ b/libtextsecure/test/protocol_wrapper_test.js @@ -105,4 +105,32 @@ describe('Protocol Wrapper', function protocolWrapperDescribe() { }); }); }); + + describe('cleanOldMessageKeys', () => { + it('should clean old message keys', () => { + const messageKeys = {}; + + const LIMIT = 2000; + + for (let i = 0; i < 2 * LIMIT; i += 1) { + messageKeys[i] = i; + } + + libsignal.SessionCipher.cleanOldMessageKeys(messageKeys); + + for (let i = 0; i < LIMIT; i += 1) { + assert( + !Object.prototype.hasOwnProperty.call(messageKeys, i), + `should delete old key ${i}` + ); + } + + for (let i = LIMIT; i < 2 * LIMIT; i += 1) { + assert( + Object.prototype.hasOwnProperty.call(messageKeys, i), + `should have fresh key ${i}` + ); + } + }); + }); });