From 158b575885a237111a5e1459c015f8005c1345ab Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Tue, 21 Nov 2017 15:20:07 -0800 Subject: [PATCH] Proper session reset: Delete sessions before and after (#1796) * web requests: Don't fail if JSON not well-formed in error cases Turns out that before our move to node-fetch, we ignored JSON.parse() failures: https://github.com/WhisperSystems/Signal-Desktop/pull/1552/files#diff-1103a6aff0f28b6066715c6994278767L37518 * Update to libsignal v1.2.0, delete sessions on reset session Previously we only archived sessions when sending or receiving a 'reset secure session, which didn't match up with the mobile apps. --- js/libtextsecure.js | 242 ++++++++++++++++------------ js/models/conversations.js | 2 +- libtextsecure/api.js | 11 +- libtextsecure/libsignal-protocol.js | 155 +++++++++--------- libtextsecure/message_receiver.js | 4 +- libtextsecure/sendmessage.js | 72 ++++++--- 6 files changed, 285 insertions(+), 201 deletions(-) diff --git a/js/libtextsecure.js b/js/libtextsecure.js index 04ce5806f..b452eac81 100644 --- a/js/libtextsecure.js +++ b/js/libtextsecure.js @@ -25538,21 +25538,18 @@ Curve25519Worker.prototype = { /** * The low 32 bits as a signed value. * @type {number} - * @expose */ this.low = low | 0; /** * The high 32 bits as a signed value. * @type {number} - * @expose */ this.high = high | 0; /** * Whether unsigned or not. * @type {boolean} - * @expose */ this.unsigned = !!unsigned; } @@ -25578,10 +25575,9 @@ Curve25519Worker.prototype = { * An indicator used to reliably determine if an object is a Long or not. * @type {boolean} * @const - * @expose * @private */ - Long.__isLong__; + Long.prototype.__isLong__; Object.defineProperty(Long.prototype, "__isLong__", { value: true, @@ -25604,7 +25600,6 @@ Curve25519Worker.prototype = { * @function * @param {*} obj Object * @returns {boolean} - * @expose */ Long.isLong = isLong; @@ -25661,7 +25656,6 @@ Curve25519Worker.prototype = { * @param {number} value The 32 bit integer in question * @param {boolean=} unsigned Whether unsigned or not, defaults to `false` for signed * @returns {!Long} The corresponding Long value - * @expose */ Long.fromInt = fromInt; @@ -25696,7 +25690,6 @@ Curve25519Worker.prototype = { * @param {number} value The number in question * @param {boolean=} unsigned Whether unsigned or not, defaults to `false` for signed * @returns {!Long} The corresponding Long value - * @expose */ Long.fromNumber = fromNumber; @@ -25719,7 +25712,6 @@ Curve25519Worker.prototype = { * @param {number} highBits The high 32 bits * @param {boolean=} unsigned Whether unsigned or not, defaults to `false` for signed * @returns {!Long} The corresponding Long value - * @expose */ Long.fromBits = fromBits; @@ -25789,7 +25781,6 @@ Curve25519Worker.prototype = { * @param {(boolean|number)=} unsigned Whether unsigned or not, defaults to `false` for signed * @param {number=} radix The radix in which the text is written (2-36), defaults to 10 * @returns {!Long} The corresponding Long value - * @expose */ Long.fromString = fromString; @@ -25815,7 +25806,6 @@ Curve25519Worker.prototype = { * @function * @param {!Long|number|string|!{low: number, high: number, unsigned: boolean}} val Value * @returns {!Long} - * @expose */ Long.fromValue = fromValue; @@ -25873,7 +25863,6 @@ Curve25519Worker.prototype = { /** * Signed zero. * @type {!Long} - * @expose */ Long.ZERO = ZERO; @@ -25886,7 +25875,6 @@ Curve25519Worker.prototype = { /** * Unsigned zero. * @type {!Long} - * @expose */ Long.UZERO = UZERO; @@ -25899,7 +25887,6 @@ Curve25519Worker.prototype = { /** * Signed one. * @type {!Long} - * @expose */ Long.ONE = ONE; @@ -25912,7 +25899,6 @@ Curve25519Worker.prototype = { /** * Unsigned one. * @type {!Long} - * @expose */ Long.UONE = UONE; @@ -25925,7 +25911,6 @@ Curve25519Worker.prototype = { /** * Signed negative one. * @type {!Long} - * @expose */ Long.NEG_ONE = NEG_ONE; @@ -25938,7 +25923,6 @@ Curve25519Worker.prototype = { /** * Maximum signed value. * @type {!Long} - * @expose */ Long.MAX_VALUE = MAX_VALUE; @@ -25951,7 +25935,6 @@ Curve25519Worker.prototype = { /** * Maximum unsigned value. * @type {!Long} - * @expose */ Long.MAX_UNSIGNED_VALUE = MAX_UNSIGNED_VALUE; @@ -25964,7 +25947,6 @@ Curve25519Worker.prototype = { /** * Minimum signed value. * @type {!Long} - * @expose */ Long.MIN_VALUE = MIN_VALUE; @@ -25977,7 +25959,6 @@ Curve25519Worker.prototype = { /** * Converts the Long to a 32 bit integer, assuming it is a 32 bit integer. * @returns {number} - * @expose */ LongPrototype.toInt = function toInt() { return this.unsigned ? this.low >>> 0 : this.low; @@ -25986,7 +25967,6 @@ Curve25519Worker.prototype = { /** * Converts the Long to a the nearest floating-point representation of this value (double, 53 bit mantissa). * @returns {number} - * @expose */ LongPrototype.toNumber = function toNumber() { if (this.unsigned) @@ -26000,7 +25980,6 @@ Curve25519Worker.prototype = { * @returns {string} * @override * @throws {RangeError} If `radix` is out of range - * @expose */ LongPrototype.toString = function toString(radix) { radix = radix || 10; @@ -26043,7 +26022,6 @@ Curve25519Worker.prototype = { /** * Gets the high 32 bits as a signed integer. * @returns {number} Signed high bits - * @expose */ LongPrototype.getHighBits = function getHighBits() { return this.high; @@ -26052,7 +26030,6 @@ Curve25519Worker.prototype = { /** * Gets the high 32 bits as an unsigned integer. * @returns {number} Unsigned high bits - * @expose */ LongPrototype.getHighBitsUnsigned = function getHighBitsUnsigned() { return this.high >>> 0; @@ -26061,7 +26038,6 @@ Curve25519Worker.prototype = { /** * Gets the low 32 bits as a signed integer. * @returns {number} Signed low bits - * @expose */ LongPrototype.getLowBits = function getLowBits() { return this.low; @@ -26070,7 +26046,6 @@ Curve25519Worker.prototype = { /** * Gets the low 32 bits as an unsigned integer. * @returns {number} Unsigned low bits - * @expose */ LongPrototype.getLowBitsUnsigned = function getLowBitsUnsigned() { return this.low >>> 0; @@ -26079,7 +26054,6 @@ Curve25519Worker.prototype = { /** * Gets the number of bits needed to represent the absolute value of this Long. * @returns {number} - * @expose */ LongPrototype.getNumBitsAbs = function getNumBitsAbs() { if (this.isNegative()) // Unsigned Longs are never negative @@ -26094,7 +26068,6 @@ Curve25519Worker.prototype = { /** * Tests if this Long's value equals zero. * @returns {boolean} - * @expose */ LongPrototype.isZero = function isZero() { return this.high === 0 && this.low === 0; @@ -26103,7 +26076,6 @@ Curve25519Worker.prototype = { /** * Tests if this Long's value is negative. * @returns {boolean} - * @expose */ LongPrototype.isNegative = function isNegative() { return !this.unsigned && this.high < 0; @@ -26112,7 +26084,6 @@ Curve25519Worker.prototype = { /** * Tests if this Long's value is positive. * @returns {boolean} - * @expose */ LongPrototype.isPositive = function isPositive() { return this.unsigned || this.high >= 0; @@ -26121,7 +26092,6 @@ Curve25519Worker.prototype = { /** * Tests if this Long's value is odd. * @returns {boolean} - * @expose */ LongPrototype.isOdd = function isOdd() { return (this.low & 1) === 1; @@ -26130,7 +26100,6 @@ Curve25519Worker.prototype = { /** * Tests if this Long's value is even. * @returns {boolean} - * @expose */ LongPrototype.isEven = function isEven() { return (this.low & 1) === 0; @@ -26140,7 +26109,6 @@ Curve25519Worker.prototype = { * Tests if this Long's value equals the specified's. * @param {!Long|number|string} other Other value * @returns {boolean} - * @expose */ LongPrototype.equals = function equals(other) { if (!isLong(other)) @@ -26155,7 +26123,6 @@ Curve25519Worker.prototype = { * @function * @param {!Long|number|string} other Other value * @returns {boolean} - * @expose */ LongPrototype.eq = LongPrototype.equals; @@ -26163,7 +26130,6 @@ Curve25519Worker.prototype = { * Tests if this Long's value differs from the specified's. * @param {!Long|number|string} other Other value * @returns {boolean} - * @expose */ LongPrototype.notEquals = function notEquals(other) { return !this.eq(/* validates */ other); @@ -26174,7 +26140,6 @@ Curve25519Worker.prototype = { * @function * @param {!Long|number|string} other Other value * @returns {boolean} - * @expose */ LongPrototype.neq = LongPrototype.notEquals; @@ -26182,7 +26147,6 @@ Curve25519Worker.prototype = { * Tests if this Long's value is less than the specified's. * @param {!Long|number|string} other Other value * @returns {boolean} - * @expose */ LongPrototype.lessThan = function lessThan(other) { return this.comp(/* validates */ other) < 0; @@ -26193,7 +26157,6 @@ Curve25519Worker.prototype = { * @function * @param {!Long|number|string} other Other value * @returns {boolean} - * @expose */ LongPrototype.lt = LongPrototype.lessThan; @@ -26201,7 +26164,6 @@ Curve25519Worker.prototype = { * Tests if this Long's value is less than or equal the specified's. * @param {!Long|number|string} other Other value * @returns {boolean} - * @expose */ LongPrototype.lessThanOrEqual = function lessThanOrEqual(other) { return this.comp(/* validates */ other) <= 0; @@ -26212,7 +26174,6 @@ Curve25519Worker.prototype = { * @function * @param {!Long|number|string} other Other value * @returns {boolean} - * @expose */ LongPrototype.lte = LongPrototype.lessThanOrEqual; @@ -26220,7 +26181,6 @@ Curve25519Worker.prototype = { * Tests if this Long's value is greater than the specified's. * @param {!Long|number|string} other Other value * @returns {boolean} - * @expose */ LongPrototype.greaterThan = function greaterThan(other) { return this.comp(/* validates */ other) > 0; @@ -26231,7 +26191,6 @@ Curve25519Worker.prototype = { * @function * @param {!Long|number|string} other Other value * @returns {boolean} - * @expose */ LongPrototype.gt = LongPrototype.greaterThan; @@ -26239,7 +26198,6 @@ Curve25519Worker.prototype = { * Tests if this Long's value is greater than or equal the specified's. * @param {!Long|number|string} other Other value * @returns {boolean} - * @expose */ LongPrototype.greaterThanOrEqual = function greaterThanOrEqual(other) { return this.comp(/* validates */ other) >= 0; @@ -26250,7 +26208,6 @@ Curve25519Worker.prototype = { * @function * @param {!Long|number|string} other Other value * @returns {boolean} - * @expose */ LongPrototype.gte = LongPrototype.greaterThanOrEqual; @@ -26259,7 +26216,6 @@ Curve25519Worker.prototype = { * @param {!Long|number|string} other Other value * @returns {number} 0 if they are the same, 1 if the this is greater and -1 * if the given one is greater - * @expose */ LongPrototype.compare = function compare(other) { if (!isLong(other)) @@ -26285,14 +26241,12 @@ Curve25519Worker.prototype = { * @param {!Long|number|string} other Other value * @returns {number} 0 if they are the same, 1 if the this is greater and -1 * if the given one is greater - * @expose */ LongPrototype.comp = LongPrototype.compare; /** * Negates this Long's value. * @returns {!Long} Negated Long - * @expose */ LongPrototype.negate = function negate() { if (!this.unsigned && this.eq(MIN_VALUE)) @@ -26304,7 +26258,6 @@ Curve25519Worker.prototype = { * Negates this Long's value. This is an alias of {@link Long#negate}. * @function * @returns {!Long} Negated Long - * @expose */ LongPrototype.neg = LongPrototype.negate; @@ -26312,7 +26265,6 @@ Curve25519Worker.prototype = { * Returns the sum of this and the specified Long. * @param {!Long|number|string} addend Addend * @returns {!Long} Sum - * @expose */ LongPrototype.add = function add(addend) { if (!isLong(addend)) @@ -26349,7 +26301,6 @@ Curve25519Worker.prototype = { * Returns the difference of this and the specified Long. * @param {!Long|number|string} subtrahend Subtrahend * @returns {!Long} Difference - * @expose */ LongPrototype.subtract = function subtract(subtrahend) { if (!isLong(subtrahend)) @@ -26362,7 +26313,6 @@ Curve25519Worker.prototype = { * @function * @param {!Long|number|string} subtrahend Subtrahend * @returns {!Long} Difference - * @expose */ LongPrototype.sub = LongPrototype.subtract; @@ -26370,7 +26320,6 @@ Curve25519Worker.prototype = { * Returns the product of this and the specified Long. * @param {!Long|number|string} multiplier Multiplier * @returns {!Long} Product - * @expose */ LongPrototype.multiply = function multiply(multiplier) { if (this.isZero()) @@ -26438,7 +26387,6 @@ Curve25519Worker.prototype = { * @function * @param {!Long|number|string} multiplier Multiplier * @returns {!Long} Product - * @expose */ LongPrototype.mul = LongPrototype.multiply; @@ -26447,7 +26395,6 @@ Curve25519Worker.prototype = { * unsigned if this Long is unsigned. * @param {!Long|number|string} divisor Divisor * @returns {!Long} Quotient - * @expose */ LongPrototype.divide = function divide(divisor) { if (!isLong(divisor)) @@ -26458,6 +26405,8 @@ Curve25519Worker.prototype = { return this.unsigned ? UZERO : ZERO; var approx, rem, res; if (!this.unsigned) { + // This section is only relevant for signed longs and is derived from the + // closure library as a whole. if (this.eq(MIN_VALUE)) { if (divisor.eq(ONE) || divisor.eq(NEG_ONE)) return MIN_VALUE; // recall that -MIN_VALUE == MIN_VALUE @@ -26483,19 +26432,18 @@ Curve25519Worker.prototype = { return this.neg().div(divisor).neg(); } else if (divisor.isNegative()) return this.div(divisor.neg()).neg(); - } else if (!divisor.unsigned) - divisor = divisor.toUnsigned(); - - // The algorithm below has not been made for unsigned longs. It's therefore - // required to take special care of the MSB prior to running it. - if (this.unsigned) { + res = ZERO; + } else { + // The algorithm below has not been made for unsigned longs. It's therefore + // required to take special care of the MSB prior to running it. + if (!divisor.unsigned) + divisor = divisor.toUnsigned(); if (divisor.gt(this)) return UZERO; if (divisor.gt(this.shru(1))) // 15 >>> 1 = 7 ; with divisor = 8 ; true return UONE; res = UZERO; - } else - res = ZERO; + } // Repeat the following until the remainder is less than other: find a // floating-point that approximates remainder / other *from below*, add this @@ -26539,7 +26487,6 @@ Curve25519Worker.prototype = { * @function * @param {!Long|number|string} divisor Divisor * @returns {!Long} Quotient - * @expose */ LongPrototype.div = LongPrototype.divide; @@ -26547,7 +26494,6 @@ Curve25519Worker.prototype = { * Returns this Long modulo the specified. * @param {!Long|number|string} divisor Divisor * @returns {!Long} Remainder - * @expose */ LongPrototype.modulo = function modulo(divisor) { if (!isLong(divisor)) @@ -26560,14 +26506,12 @@ Curve25519Worker.prototype = { * @function * @param {!Long|number|string} divisor Divisor * @returns {!Long} Remainder - * @expose */ LongPrototype.mod = LongPrototype.modulo; /** * Returns the bitwise NOT of this Long. * @returns {!Long} - * @expose */ LongPrototype.not = function not() { return fromBits(~this.low, ~this.high, this.unsigned); @@ -26577,7 +26521,6 @@ Curve25519Worker.prototype = { * Returns the bitwise AND of this Long and the specified. * @param {!Long|number|string} other Other Long * @returns {!Long} - * @expose */ LongPrototype.and = function and(other) { if (!isLong(other)) @@ -26589,7 +26532,6 @@ Curve25519Worker.prototype = { * Returns the bitwise OR of this Long and the specified. * @param {!Long|number|string} other Other Long * @returns {!Long} - * @expose */ LongPrototype.or = function or(other) { if (!isLong(other)) @@ -26601,7 +26543,6 @@ Curve25519Worker.prototype = { * Returns the bitwise XOR of this Long and the given one. * @param {!Long|number|string} other Other Long * @returns {!Long} - * @expose */ LongPrototype.xor = function xor(other) { if (!isLong(other)) @@ -26613,7 +26554,6 @@ Curve25519Worker.prototype = { * Returns this Long with bits shifted to the left by the given amount. * @param {number|!Long} numBits Number of bits * @returns {!Long} Shifted Long - * @expose */ LongPrototype.shiftLeft = function shiftLeft(numBits) { if (isLong(numBits)) @@ -26631,7 +26571,6 @@ Curve25519Worker.prototype = { * @function * @param {number|!Long} numBits Number of bits * @returns {!Long} Shifted Long - * @expose */ LongPrototype.shl = LongPrototype.shiftLeft; @@ -26639,7 +26578,6 @@ Curve25519Worker.prototype = { * Returns this Long with bits arithmetically shifted to the right by the given amount. * @param {number|!Long} numBits Number of bits * @returns {!Long} Shifted Long - * @expose */ LongPrototype.shiftRight = function shiftRight(numBits) { if (isLong(numBits)) @@ -26657,7 +26595,6 @@ Curve25519Worker.prototype = { * @function * @param {number|!Long} numBits Number of bits * @returns {!Long} Shifted Long - * @expose */ LongPrototype.shr = LongPrototype.shiftRight; @@ -26665,7 +26602,6 @@ Curve25519Worker.prototype = { * Returns this Long with bits logically shifted to the right by the given amount. * @param {number|!Long} numBits Number of bits * @returns {!Long} Shifted Long - * @expose */ LongPrototype.shiftRightUnsigned = function shiftRightUnsigned(numBits) { if (isLong(numBits)) @@ -26690,14 +26626,12 @@ Curve25519Worker.prototype = { * @function * @param {number|!Long} numBits Number of bits * @returns {!Long} Shifted Long - * @expose */ LongPrototype.shru = LongPrototype.shiftRightUnsigned; /** * Converts this Long to signed. * @returns {!Long} Signed long - * @expose */ LongPrototype.toSigned = function toSigned() { if (!this.unsigned) @@ -26708,7 +26642,6 @@ Curve25519Worker.prototype = { /** * Converts this Long to unsigned. * @returns {!Long} Unsigned long - * @expose */ LongPrototype.toUnsigned = function toUnsigned() { if (this.unsigned) @@ -26716,6 +26649,53 @@ Curve25519Worker.prototype = { return fromBits(this.low, this.high, true); }; + /** + * Converts this Long to its byte representation. + * @param {boolean=} le Whether little or big endian, defaults to big endian + * @returns {!Array.} Byte representation + */ + LongPrototype.toBytes = function(le) { + return le ? this.toBytesLE() : this.toBytesBE(); + } + + /** + * Converts this Long to its little endian byte representation. + * @returns {!Array.} Little endian byte representation + */ + LongPrototype.toBytesLE = function() { + var hi = this.high, + lo = this.low; + return [ + lo & 0xff, + (lo >>> 8) & 0xff, + (lo >>> 16) & 0xff, + (lo >>> 24) & 0xff, + hi & 0xff, + (hi >>> 8) & 0xff, + (hi >>> 16) & 0xff, + (hi >>> 24) & 0xff + ]; + } + + /** + * Converts this Long to its big endian byte representation. + * @returns {!Array.} Big endian byte representation + */ + LongPrototype.toBytesBE = function() { + var hi = this.high, + lo = this.low; + return [ + (hi >>> 24) & 0xff, + (hi >>> 16) & 0xff, + (hi >>> 8) & 0xff, + hi & 0xff, + (lo >>> 24) & 0xff, + (lo >>> 16) & 0xff, + (lo >>> 8) & 0xff, + lo & 0xff + ]; + } + return Long; }); @@ -35901,6 +35881,10 @@ Internal.SessionRecord = function() { delete sessions[util.toString(oldestBaseKey)]; } }, + deleteAllSessions: function() { + // Used primarily in session reset scenarios, where we really delete sessions + this.sessions = {}; + } }; return SessionRecord; @@ -36396,7 +36380,7 @@ SessionCipher.prototype = { }.bind(this)); }, doDecryptWhisperMessage: function(messageBytes, session) { - if (!messageBytes instanceof ArrayBuffer) { + if (!(messageBytes instanceof ArrayBuffer)) { throw new Error("Expected messageBytes to be an ArrayBuffer"); } var version = (new Uint8Array(messageBytes))[0]; @@ -36571,6 +36555,20 @@ SessionCipher.prototype = { return this.storage.storeSession(address, record.serialize()); }.bind(this)); }.bind(this)); + }, + deleteAllSessionsForDevice: function() { + // Used in session reset scenarios, where we really need to delete + var address = this.remoteAddress.toString(); + return Internal.SessionLock.queueJobForNumber(address, function() { + return this.getRecord(address).then(function(record) { + if (record === undefined) { + return; + } + + record.deleteAllSessions(); + return this.storage.storeSession(address, record.serialize()); + }.bind(this)); + }.bind(this)); } }; @@ -36590,6 +36588,7 @@ libsignal.SessionCipher = function(storage, remoteAddress, options) { this.getRemoteRegistrationId = cipher.getRemoteRegistrationId.bind(cipher); this.hasOpenSession = cipher.hasOpenSession.bind(cipher); this.closeOpenSessionForDevice = cipher.closeOpenSessionForDevice.bind(cipher); + this.deleteAllSessionsForDevice = cipher.deleteAllSessionsForDevice.bind(cipher); }; /* @@ -37502,7 +37501,16 @@ var TextSecureServer = (function() { window.nodeFetch(url, fetchOptions).then(function(response) { var resultPromise; if (options.responseType === 'json') { - resultPromise = response.json(); + resultPromise = response.json().catch(function(error) { + // If the response was otherwise successful, a JSON.parse() failure really + // is a problem. But the Signal server does return HTML in error cases + // when we requested JSON, sadly. + if (0 <= response.status && response.status < 400) { + throw error; + } + + return null; + }) } else if (!options.responseType || options.responseType === 'text') { resultPromise = response.text(); } else if (options.responseType === 'arraybuffer') { @@ -39250,8 +39258,8 @@ MessageReceiver.prototype.extend({ var address = new libsignal.SignalProtocolAddress(number, deviceId); var sessionCipher = new libsignal.SessionCipher(textsecure.storage.protocol, address); - console.log('closing session for', address.toString()); - return sessionCipher.closeOpenSessionForDevice(); + console.log('deleting sessions for', address.toString()); + return sessionCipher.deleteAllSessionsForDevice(); })); }); }, @@ -39888,12 +39896,14 @@ MessageSender.prototype = { sendIndividualProto: function(number, proto, timestamp, silent) { return new Promise(function(resolve, reject) { - this.sendMessageProto(timestamp, [number], proto, function(res) { - if (res.errors.length > 0) + var callback = function(res) { + if (res.errors.length > 0) { reject(res); - else + } else { resolve(res); - }, silent); + } + }; + this.sendMessageProto(timestamp, [number], proto, callback, silent); }.bind(this)); }, @@ -40068,10 +40078,11 @@ MessageSender.prototype = { return new Promise(function(resolve, reject) { this.sendMessageProto(timestamp, numbers, proto, function(res) { res.dataMessage = proto.toArrayBuffer(); - if (res.errors.length > 0) + if (res.errors.length > 0) { reject(res); - else + } else { resolve(res); + } }.bind(this)); }.bind(this)); }, @@ -40088,25 +40099,56 @@ MessageSender.prototype = { }); }, - closeSession: function(number, timestamp) { - console.log('sending end session'); + resetSession: function(number, timestamp) { + console.log('resetting secure session'); var proto = new textsecure.protobuf.DataMessage(); proto.body = "TERMINATE"; proto.flags = textsecure.protobuf.DataMessage.Flags.END_SESSION; - return this.sendIndividualProto(number, proto, timestamp).then(function(res) { - return this.sendSyncMessage(proto.toArrayBuffer(), timestamp, number).then(function() { - return textsecure.storage.protocol.getDeviceIds(number).then(function(deviceIds) { + + var logError = function(prefix) { + return function(error) { + console.log( + prefix, + error && error.stack ? error.stack : error + ); + throw error; + }; + }; + var deleteAllSessions = function(number) { + return textsecure.storage.protocol.getDeviceIds(number) + .then(function(deviceIds) { return Promise.all(deviceIds.map(function(deviceId) { var address = new libsignal.SignalProtocolAddress(number, deviceId); - console.log('closing session for', address.toString()); - var sessionCipher = new libsignal.SessionCipher(textsecure.storage.protocol, address); - return sessionCipher.closeOpenSessionForDevice(); - })).then(function() { - return res; - }); + console.log('deleting sessions for', address.toString()); + var sessionCipher = new libsignal.SessionCipher( + textsecure.storage.protocol, + address + ); + return sessionCipher.deleteAllSessionsForDevice(); + })); }); + }; + + var sendToContact = deleteAllSessions(number) + .catch(logError('resetSession/deleteAllSessions1 error:')) + .then(function() { + console.log('finished closing local sessions, now sending to contact'); + return this.sendIndividualProto(number, proto, timestamp) + .catch(logError('resetSession/sendToContact error:')) + }.bind(this)) + .then(function() { + return deleteAllSessions(number) + .catch(logError('resetSession/deleteAllSessions2 error:')); }); - }.bind(this)); + + var buffer = proto.toArrayBuffer(); + var sendSync = this.sendSyncMessage(buffer, timestamp, number) + .catch(logError('resetSession/sendSync error:')); + + return Promise.all([ + sendToContact, + sendSync + ]); }, sendMessageToGroup: function(groupId, messageText, attachments, timestamp, expireTimer, profileKey) { @@ -40295,7 +40337,7 @@ textsecure.MessageSender = function(url, username, password, cdn_url) { this.sendRequestContactSyncMessage = sender.sendRequestContactSyncMessage .bind(sender); this.sendRequestConfigurationSyncMessage = sender.sendRequestConfigurationSyncMessage.bind(sender); this.sendMessageToNumber = sender.sendMessageToNumber .bind(sender); - this.closeSession = sender.closeSession .bind(sender); + this.resetSession = sender.resetSession .bind(sender); this.sendMessageToGroup = sender.sendMessageToGroup .bind(sender); this.createGroup = sender.createGroup .bind(sender); this.updateGroup = sender.updateGroup .bind(sender); diff --git a/js/models/conversations.js b/js/models/conversations.js index 766f31de5..4d98350c1 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -718,7 +718,7 @@ recipients : this.getRecipients(), flags : textsecure.protobuf.DataMessage.Flags.END_SESSION }); - message.send(textsecure.messaging.closeSession(this.id, now)); + message.send(textsecure.messaging.resetSession(this.id, now)); } }, diff --git a/libtextsecure/api.js b/libtextsecure/api.js index 73e5f08a0..b1d781310 100644 --- a/libtextsecure/api.js +++ b/libtextsecure/api.js @@ -65,7 +65,16 @@ var TextSecureServer = (function() { window.nodeFetch(url, fetchOptions).then(function(response) { var resultPromise; if (options.responseType === 'json') { - resultPromise = response.json(); + resultPromise = response.json().catch(function(error) { + // If the response was otherwise successful, a JSON.parse() failure really + // is a problem. But the Signal server does return HTML in error cases + // when we requested JSON, sadly. + if (0 <= response.status && response.status < 400) { + throw error; + } + + return null; + }) } else if (!options.responseType || options.responseType === 'text') { resultPromise = response.text(); } else if (options.responseType === 'arraybuffer') { diff --git a/libtextsecure/libsignal-protocol.js b/libtextsecure/libsignal-protocol.js index d726af42e..0b825072a 100644 --- a/libtextsecure/libsignal-protocol.js +++ b/libtextsecure/libsignal-protocol.js @@ -25399,21 +25399,18 @@ Curve25519Worker.prototype = { /** * The low 32 bits as a signed value. * @type {number} - * @expose */ this.low = low | 0; /** * The high 32 bits as a signed value. * @type {number} - * @expose */ this.high = high | 0; /** * Whether unsigned or not. * @type {boolean} - * @expose */ this.unsigned = !!unsigned; } @@ -25439,10 +25436,9 @@ Curve25519Worker.prototype = { * An indicator used to reliably determine if an object is a Long or not. * @type {boolean} * @const - * @expose * @private */ - Long.__isLong__; + Long.prototype.__isLong__; Object.defineProperty(Long.prototype, "__isLong__", { value: true, @@ -25465,7 +25461,6 @@ Curve25519Worker.prototype = { * @function * @param {*} obj Object * @returns {boolean} - * @expose */ Long.isLong = isLong; @@ -25522,7 +25517,6 @@ Curve25519Worker.prototype = { * @param {number} value The 32 bit integer in question * @param {boolean=} unsigned Whether unsigned or not, defaults to `false` for signed * @returns {!Long} The corresponding Long value - * @expose */ Long.fromInt = fromInt; @@ -25557,7 +25551,6 @@ Curve25519Worker.prototype = { * @param {number} value The number in question * @param {boolean=} unsigned Whether unsigned or not, defaults to `false` for signed * @returns {!Long} The corresponding Long value - * @expose */ Long.fromNumber = fromNumber; @@ -25580,7 +25573,6 @@ Curve25519Worker.prototype = { * @param {number} highBits The high 32 bits * @param {boolean=} unsigned Whether unsigned or not, defaults to `false` for signed * @returns {!Long} The corresponding Long value - * @expose */ Long.fromBits = fromBits; @@ -25650,7 +25642,6 @@ Curve25519Worker.prototype = { * @param {(boolean|number)=} unsigned Whether unsigned or not, defaults to `false` for signed * @param {number=} radix The radix in which the text is written (2-36), defaults to 10 * @returns {!Long} The corresponding Long value - * @expose */ Long.fromString = fromString; @@ -25676,7 +25667,6 @@ Curve25519Worker.prototype = { * @function * @param {!Long|number|string|!{low: number, high: number, unsigned: boolean}} val Value * @returns {!Long} - * @expose */ Long.fromValue = fromValue; @@ -25734,7 +25724,6 @@ Curve25519Worker.prototype = { /** * Signed zero. * @type {!Long} - * @expose */ Long.ZERO = ZERO; @@ -25747,7 +25736,6 @@ Curve25519Worker.prototype = { /** * Unsigned zero. * @type {!Long} - * @expose */ Long.UZERO = UZERO; @@ -25760,7 +25748,6 @@ Curve25519Worker.prototype = { /** * Signed one. * @type {!Long} - * @expose */ Long.ONE = ONE; @@ -25773,7 +25760,6 @@ Curve25519Worker.prototype = { /** * Unsigned one. * @type {!Long} - * @expose */ Long.UONE = UONE; @@ -25786,7 +25772,6 @@ Curve25519Worker.prototype = { /** * Signed negative one. * @type {!Long} - * @expose */ Long.NEG_ONE = NEG_ONE; @@ -25799,7 +25784,6 @@ Curve25519Worker.prototype = { /** * Maximum signed value. * @type {!Long} - * @expose */ Long.MAX_VALUE = MAX_VALUE; @@ -25812,7 +25796,6 @@ Curve25519Worker.prototype = { /** * Maximum unsigned value. * @type {!Long} - * @expose */ Long.MAX_UNSIGNED_VALUE = MAX_UNSIGNED_VALUE; @@ -25825,7 +25808,6 @@ Curve25519Worker.prototype = { /** * Minimum signed value. * @type {!Long} - * @expose */ Long.MIN_VALUE = MIN_VALUE; @@ -25838,7 +25820,6 @@ Curve25519Worker.prototype = { /** * Converts the Long to a 32 bit integer, assuming it is a 32 bit integer. * @returns {number} - * @expose */ LongPrototype.toInt = function toInt() { return this.unsigned ? this.low >>> 0 : this.low; @@ -25847,7 +25828,6 @@ Curve25519Worker.prototype = { /** * Converts the Long to a the nearest floating-point representation of this value (double, 53 bit mantissa). * @returns {number} - * @expose */ LongPrototype.toNumber = function toNumber() { if (this.unsigned) @@ -25861,7 +25841,6 @@ Curve25519Worker.prototype = { * @returns {string} * @override * @throws {RangeError} If `radix` is out of range - * @expose */ LongPrototype.toString = function toString(radix) { radix = radix || 10; @@ -25904,7 +25883,6 @@ Curve25519Worker.prototype = { /** * Gets the high 32 bits as a signed integer. * @returns {number} Signed high bits - * @expose */ LongPrototype.getHighBits = function getHighBits() { return this.high; @@ -25913,7 +25891,6 @@ Curve25519Worker.prototype = { /** * Gets the high 32 bits as an unsigned integer. * @returns {number} Unsigned high bits - * @expose */ LongPrototype.getHighBitsUnsigned = function getHighBitsUnsigned() { return this.high >>> 0; @@ -25922,7 +25899,6 @@ Curve25519Worker.prototype = { /** * Gets the low 32 bits as a signed integer. * @returns {number} Signed low bits - * @expose */ LongPrototype.getLowBits = function getLowBits() { return this.low; @@ -25931,7 +25907,6 @@ Curve25519Worker.prototype = { /** * Gets the low 32 bits as an unsigned integer. * @returns {number} Unsigned low bits - * @expose */ LongPrototype.getLowBitsUnsigned = function getLowBitsUnsigned() { return this.low >>> 0; @@ -25940,7 +25915,6 @@ Curve25519Worker.prototype = { /** * Gets the number of bits needed to represent the absolute value of this Long. * @returns {number} - * @expose */ LongPrototype.getNumBitsAbs = function getNumBitsAbs() { if (this.isNegative()) // Unsigned Longs are never negative @@ -25955,7 +25929,6 @@ Curve25519Worker.prototype = { /** * Tests if this Long's value equals zero. * @returns {boolean} - * @expose */ LongPrototype.isZero = function isZero() { return this.high === 0 && this.low === 0; @@ -25964,7 +25937,6 @@ Curve25519Worker.prototype = { /** * Tests if this Long's value is negative. * @returns {boolean} - * @expose */ LongPrototype.isNegative = function isNegative() { return !this.unsigned && this.high < 0; @@ -25973,7 +25945,6 @@ Curve25519Worker.prototype = { /** * Tests if this Long's value is positive. * @returns {boolean} - * @expose */ LongPrototype.isPositive = function isPositive() { return this.unsigned || this.high >= 0; @@ -25982,7 +25953,6 @@ Curve25519Worker.prototype = { /** * Tests if this Long's value is odd. * @returns {boolean} - * @expose */ LongPrototype.isOdd = function isOdd() { return (this.low & 1) === 1; @@ -25991,7 +25961,6 @@ Curve25519Worker.prototype = { /** * Tests if this Long's value is even. * @returns {boolean} - * @expose */ LongPrototype.isEven = function isEven() { return (this.low & 1) === 0; @@ -26001,7 +25970,6 @@ Curve25519Worker.prototype = { * Tests if this Long's value equals the specified's. * @param {!Long|number|string} other Other value * @returns {boolean} - * @expose */ LongPrototype.equals = function equals(other) { if (!isLong(other)) @@ -26016,7 +25984,6 @@ Curve25519Worker.prototype = { * @function * @param {!Long|number|string} other Other value * @returns {boolean} - * @expose */ LongPrototype.eq = LongPrototype.equals; @@ -26024,7 +25991,6 @@ Curve25519Worker.prototype = { * Tests if this Long's value differs from the specified's. * @param {!Long|number|string} other Other value * @returns {boolean} - * @expose */ LongPrototype.notEquals = function notEquals(other) { return !this.eq(/* validates */ other); @@ -26035,7 +26001,6 @@ Curve25519Worker.prototype = { * @function * @param {!Long|number|string} other Other value * @returns {boolean} - * @expose */ LongPrototype.neq = LongPrototype.notEquals; @@ -26043,7 +26008,6 @@ Curve25519Worker.prototype = { * Tests if this Long's value is less than the specified's. * @param {!Long|number|string} other Other value * @returns {boolean} - * @expose */ LongPrototype.lessThan = function lessThan(other) { return this.comp(/* validates */ other) < 0; @@ -26054,7 +26018,6 @@ Curve25519Worker.prototype = { * @function * @param {!Long|number|string} other Other value * @returns {boolean} - * @expose */ LongPrototype.lt = LongPrototype.lessThan; @@ -26062,7 +26025,6 @@ Curve25519Worker.prototype = { * Tests if this Long's value is less than or equal the specified's. * @param {!Long|number|string} other Other value * @returns {boolean} - * @expose */ LongPrototype.lessThanOrEqual = function lessThanOrEqual(other) { return this.comp(/* validates */ other) <= 0; @@ -26073,7 +26035,6 @@ Curve25519Worker.prototype = { * @function * @param {!Long|number|string} other Other value * @returns {boolean} - * @expose */ LongPrototype.lte = LongPrototype.lessThanOrEqual; @@ -26081,7 +26042,6 @@ Curve25519Worker.prototype = { * Tests if this Long's value is greater than the specified's. * @param {!Long|number|string} other Other value * @returns {boolean} - * @expose */ LongPrototype.greaterThan = function greaterThan(other) { return this.comp(/* validates */ other) > 0; @@ -26092,7 +26052,6 @@ Curve25519Worker.prototype = { * @function * @param {!Long|number|string} other Other value * @returns {boolean} - * @expose */ LongPrototype.gt = LongPrototype.greaterThan; @@ -26100,7 +26059,6 @@ Curve25519Worker.prototype = { * Tests if this Long's value is greater than or equal the specified's. * @param {!Long|number|string} other Other value * @returns {boolean} - * @expose */ LongPrototype.greaterThanOrEqual = function greaterThanOrEqual(other) { return this.comp(/* validates */ other) >= 0; @@ -26111,7 +26069,6 @@ Curve25519Worker.prototype = { * @function * @param {!Long|number|string} other Other value * @returns {boolean} - * @expose */ LongPrototype.gte = LongPrototype.greaterThanOrEqual; @@ -26120,7 +26077,6 @@ Curve25519Worker.prototype = { * @param {!Long|number|string} other Other value * @returns {number} 0 if they are the same, 1 if the this is greater and -1 * if the given one is greater - * @expose */ LongPrototype.compare = function compare(other) { if (!isLong(other)) @@ -26146,14 +26102,12 @@ Curve25519Worker.prototype = { * @param {!Long|number|string} other Other value * @returns {number} 0 if they are the same, 1 if the this is greater and -1 * if the given one is greater - * @expose */ LongPrototype.comp = LongPrototype.compare; /** * Negates this Long's value. * @returns {!Long} Negated Long - * @expose */ LongPrototype.negate = function negate() { if (!this.unsigned && this.eq(MIN_VALUE)) @@ -26165,7 +26119,6 @@ Curve25519Worker.prototype = { * Negates this Long's value. This is an alias of {@link Long#negate}. * @function * @returns {!Long} Negated Long - * @expose */ LongPrototype.neg = LongPrototype.negate; @@ -26173,7 +26126,6 @@ Curve25519Worker.prototype = { * Returns the sum of this and the specified Long. * @param {!Long|number|string} addend Addend * @returns {!Long} Sum - * @expose */ LongPrototype.add = function add(addend) { if (!isLong(addend)) @@ -26210,7 +26162,6 @@ Curve25519Worker.prototype = { * Returns the difference of this and the specified Long. * @param {!Long|number|string} subtrahend Subtrahend * @returns {!Long} Difference - * @expose */ LongPrototype.subtract = function subtract(subtrahend) { if (!isLong(subtrahend)) @@ -26223,7 +26174,6 @@ Curve25519Worker.prototype = { * @function * @param {!Long|number|string} subtrahend Subtrahend * @returns {!Long} Difference - * @expose */ LongPrototype.sub = LongPrototype.subtract; @@ -26231,7 +26181,6 @@ Curve25519Worker.prototype = { * Returns the product of this and the specified Long. * @param {!Long|number|string} multiplier Multiplier * @returns {!Long} Product - * @expose */ LongPrototype.multiply = function multiply(multiplier) { if (this.isZero()) @@ -26299,7 +26248,6 @@ Curve25519Worker.prototype = { * @function * @param {!Long|number|string} multiplier Multiplier * @returns {!Long} Product - * @expose */ LongPrototype.mul = LongPrototype.multiply; @@ -26308,7 +26256,6 @@ Curve25519Worker.prototype = { * unsigned if this Long is unsigned. * @param {!Long|number|string} divisor Divisor * @returns {!Long} Quotient - * @expose */ LongPrototype.divide = function divide(divisor) { if (!isLong(divisor)) @@ -26319,6 +26266,8 @@ Curve25519Worker.prototype = { return this.unsigned ? UZERO : ZERO; var approx, rem, res; if (!this.unsigned) { + // This section is only relevant for signed longs and is derived from the + // closure library as a whole. if (this.eq(MIN_VALUE)) { if (divisor.eq(ONE) || divisor.eq(NEG_ONE)) return MIN_VALUE; // recall that -MIN_VALUE == MIN_VALUE @@ -26344,19 +26293,18 @@ Curve25519Worker.prototype = { return this.neg().div(divisor).neg(); } else if (divisor.isNegative()) return this.div(divisor.neg()).neg(); - } else if (!divisor.unsigned) - divisor = divisor.toUnsigned(); - - // The algorithm below has not been made for unsigned longs. It's therefore - // required to take special care of the MSB prior to running it. - if (this.unsigned) { + res = ZERO; + } else { + // The algorithm below has not been made for unsigned longs. It's therefore + // required to take special care of the MSB prior to running it. + if (!divisor.unsigned) + divisor = divisor.toUnsigned(); if (divisor.gt(this)) return UZERO; if (divisor.gt(this.shru(1))) // 15 >>> 1 = 7 ; with divisor = 8 ; true return UONE; res = UZERO; - } else - res = ZERO; + } // Repeat the following until the remainder is less than other: find a // floating-point that approximates remainder / other *from below*, add this @@ -26400,7 +26348,6 @@ Curve25519Worker.prototype = { * @function * @param {!Long|number|string} divisor Divisor * @returns {!Long} Quotient - * @expose */ LongPrototype.div = LongPrototype.divide; @@ -26408,7 +26355,6 @@ Curve25519Worker.prototype = { * Returns this Long modulo the specified. * @param {!Long|number|string} divisor Divisor * @returns {!Long} Remainder - * @expose */ LongPrototype.modulo = function modulo(divisor) { if (!isLong(divisor)) @@ -26421,14 +26367,12 @@ Curve25519Worker.prototype = { * @function * @param {!Long|number|string} divisor Divisor * @returns {!Long} Remainder - * @expose */ LongPrototype.mod = LongPrototype.modulo; /** * Returns the bitwise NOT of this Long. * @returns {!Long} - * @expose */ LongPrototype.not = function not() { return fromBits(~this.low, ~this.high, this.unsigned); @@ -26438,7 +26382,6 @@ Curve25519Worker.prototype = { * Returns the bitwise AND of this Long and the specified. * @param {!Long|number|string} other Other Long * @returns {!Long} - * @expose */ LongPrototype.and = function and(other) { if (!isLong(other)) @@ -26450,7 +26393,6 @@ Curve25519Worker.prototype = { * Returns the bitwise OR of this Long and the specified. * @param {!Long|number|string} other Other Long * @returns {!Long} - * @expose */ LongPrototype.or = function or(other) { if (!isLong(other)) @@ -26462,7 +26404,6 @@ Curve25519Worker.prototype = { * Returns the bitwise XOR of this Long and the given one. * @param {!Long|number|string} other Other Long * @returns {!Long} - * @expose */ LongPrototype.xor = function xor(other) { if (!isLong(other)) @@ -26474,7 +26415,6 @@ Curve25519Worker.prototype = { * Returns this Long with bits shifted to the left by the given amount. * @param {number|!Long} numBits Number of bits * @returns {!Long} Shifted Long - * @expose */ LongPrototype.shiftLeft = function shiftLeft(numBits) { if (isLong(numBits)) @@ -26492,7 +26432,6 @@ Curve25519Worker.prototype = { * @function * @param {number|!Long} numBits Number of bits * @returns {!Long} Shifted Long - * @expose */ LongPrototype.shl = LongPrototype.shiftLeft; @@ -26500,7 +26439,6 @@ Curve25519Worker.prototype = { * Returns this Long with bits arithmetically shifted to the right by the given amount. * @param {number|!Long} numBits Number of bits * @returns {!Long} Shifted Long - * @expose */ LongPrototype.shiftRight = function shiftRight(numBits) { if (isLong(numBits)) @@ -26518,7 +26456,6 @@ Curve25519Worker.prototype = { * @function * @param {number|!Long} numBits Number of bits * @returns {!Long} Shifted Long - * @expose */ LongPrototype.shr = LongPrototype.shiftRight; @@ -26526,7 +26463,6 @@ Curve25519Worker.prototype = { * Returns this Long with bits logically shifted to the right by the given amount. * @param {number|!Long} numBits Number of bits * @returns {!Long} Shifted Long - * @expose */ LongPrototype.shiftRightUnsigned = function shiftRightUnsigned(numBits) { if (isLong(numBits)) @@ -26551,14 +26487,12 @@ Curve25519Worker.prototype = { * @function * @param {number|!Long} numBits Number of bits * @returns {!Long} Shifted Long - * @expose */ LongPrototype.shru = LongPrototype.shiftRightUnsigned; /** * Converts this Long to signed. * @returns {!Long} Signed long - * @expose */ LongPrototype.toSigned = function toSigned() { if (!this.unsigned) @@ -26569,7 +26503,6 @@ Curve25519Worker.prototype = { /** * Converts this Long to unsigned. * @returns {!Long} Unsigned long - * @expose */ LongPrototype.toUnsigned = function toUnsigned() { if (this.unsigned) @@ -26577,6 +26510,53 @@ Curve25519Worker.prototype = { return fromBits(this.low, this.high, true); }; + /** + * Converts this Long to its byte representation. + * @param {boolean=} le Whether little or big endian, defaults to big endian + * @returns {!Array.} Byte representation + */ + LongPrototype.toBytes = function(le) { + return le ? this.toBytesLE() : this.toBytesBE(); + } + + /** + * Converts this Long to its little endian byte representation. + * @returns {!Array.} Little endian byte representation + */ + LongPrototype.toBytesLE = function() { + var hi = this.high, + lo = this.low; + return [ + lo & 0xff, + (lo >>> 8) & 0xff, + (lo >>> 16) & 0xff, + (lo >>> 24) & 0xff, + hi & 0xff, + (hi >>> 8) & 0xff, + (hi >>> 16) & 0xff, + (hi >>> 24) & 0xff + ]; + } + + /** + * Converts this Long to its big endian byte representation. + * @returns {!Array.} Big endian byte representation + */ + LongPrototype.toBytesBE = function() { + var hi = this.high, + lo = this.low; + return [ + (hi >>> 24) & 0xff, + (hi >>> 16) & 0xff, + (hi >>> 8) & 0xff, + hi & 0xff, + (lo >>> 24) & 0xff, + (lo >>> 16) & 0xff, + (lo >>> 8) & 0xff, + lo & 0xff + ]; + } + return Long; }); @@ -35762,6 +35742,10 @@ Internal.SessionRecord = function() { delete sessions[util.toString(oldestBaseKey)]; } }, + deleteAllSessions: function() { + // Used primarily in session reset scenarios, where we really delete sessions + this.sessions = {}; + } }; return SessionRecord; @@ -36257,7 +36241,7 @@ SessionCipher.prototype = { }.bind(this)); }, doDecryptWhisperMessage: function(messageBytes, session) { - if (!messageBytes instanceof ArrayBuffer) { + if (!(messageBytes instanceof ArrayBuffer)) { throw new Error("Expected messageBytes to be an ArrayBuffer"); } var version = (new Uint8Array(messageBytes))[0]; @@ -36432,6 +36416,20 @@ SessionCipher.prototype = { return this.storage.storeSession(address, record.serialize()); }.bind(this)); }.bind(this)); + }, + deleteAllSessionsForDevice: function() { + // Used in session reset scenarios, where we really need to delete + var address = this.remoteAddress.toString(); + return Internal.SessionLock.queueJobForNumber(address, function() { + return this.getRecord(address).then(function(record) { + if (record === undefined) { + return; + } + + record.deleteAllSessions(); + return this.storage.storeSession(address, record.serialize()); + }.bind(this)); + }.bind(this)); } }; @@ -36451,6 +36449,7 @@ libsignal.SessionCipher = function(storage, remoteAddress, options) { this.getRemoteRegistrationId = cipher.getRemoteRegistrationId.bind(cipher); this.hasOpenSession = cipher.hasOpenSession.bind(cipher); this.closeOpenSessionForDevice = cipher.closeOpenSessionForDevice.bind(cipher); + this.deleteAllSessionsForDevice = cipher.deleteAllSessionsForDevice.bind(cipher); }; /* diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js index 7c32df699..3d60f11c8 100644 --- a/libtextsecure/message_receiver.js +++ b/libtextsecure/message_receiver.js @@ -844,8 +844,8 @@ MessageReceiver.prototype.extend({ var address = new libsignal.SignalProtocolAddress(number, deviceId); var sessionCipher = new libsignal.SessionCipher(textsecure.storage.protocol, address); - console.log('closing session for', address.toString()); - return sessionCipher.closeOpenSessionForDevice(); + console.log('deleting sessions for', address.toString()); + return sessionCipher.deleteAllSessionsForDevice(); })); }); }, diff --git a/libtextsecure/sendmessage.js b/libtextsecure/sendmessage.js index b0fb6b81c..ba55f97bb 100644 --- a/libtextsecure/sendmessage.js +++ b/libtextsecure/sendmessage.js @@ -275,12 +275,14 @@ MessageSender.prototype = { sendIndividualProto: function(number, proto, timestamp, silent) { return new Promise(function(resolve, reject) { - this.sendMessageProto(timestamp, [number], proto, function(res) { - if (res.errors.length > 0) + var callback = function(res) { + if (res.errors.length > 0) { reject(res); - else + } else { resolve(res); - }, silent); + } + }; + this.sendMessageProto(timestamp, [number], proto, callback, silent); }.bind(this)); }, @@ -455,10 +457,11 @@ MessageSender.prototype = { return new Promise(function(resolve, reject) { this.sendMessageProto(timestamp, numbers, proto, function(res) { res.dataMessage = proto.toArrayBuffer(); - if (res.errors.length > 0) + if (res.errors.length > 0) { reject(res); - else + } else { resolve(res); + } }.bind(this)); }.bind(this)); }, @@ -475,25 +478,56 @@ MessageSender.prototype = { }); }, - closeSession: function(number, timestamp) { - console.log('sending end session'); + resetSession: function(number, timestamp) { + console.log('resetting secure session'); var proto = new textsecure.protobuf.DataMessage(); proto.body = "TERMINATE"; proto.flags = textsecure.protobuf.DataMessage.Flags.END_SESSION; - return this.sendIndividualProto(number, proto, timestamp).then(function(res) { - return this.sendSyncMessage(proto.toArrayBuffer(), timestamp, number).then(function() { - return textsecure.storage.protocol.getDeviceIds(number).then(function(deviceIds) { + + var logError = function(prefix) { + return function(error) { + console.log( + prefix, + error && error.stack ? error.stack : error + ); + throw error; + }; + }; + var deleteAllSessions = function(number) { + return textsecure.storage.protocol.getDeviceIds(number) + .then(function(deviceIds) { return Promise.all(deviceIds.map(function(deviceId) { var address = new libsignal.SignalProtocolAddress(number, deviceId); - console.log('closing session for', address.toString()); - var sessionCipher = new libsignal.SessionCipher(textsecure.storage.protocol, address); - return sessionCipher.closeOpenSessionForDevice(); - })).then(function() { - return res; - }); + console.log('deleting sessions for', address.toString()); + var sessionCipher = new libsignal.SessionCipher( + textsecure.storage.protocol, + address + ); + return sessionCipher.deleteAllSessionsForDevice(); + })); }); + }; + + var sendToContact = deleteAllSessions(number) + .catch(logError('resetSession/deleteAllSessions1 error:')) + .then(function() { + console.log('finished closing local sessions, now sending to contact'); + return this.sendIndividualProto(number, proto, timestamp) + .catch(logError('resetSession/sendToContact error:')) + }.bind(this)) + .then(function() { + return deleteAllSessions(number) + .catch(logError('resetSession/deleteAllSessions2 error:')); }); - }.bind(this)); + + var buffer = proto.toArrayBuffer(); + var sendSync = this.sendSyncMessage(buffer, timestamp, number) + .catch(logError('resetSession/sendSync error:')); + + return Promise.all([ + sendToContact, + sendSync + ]); }, sendMessageToGroup: function(groupId, messageText, attachments, timestamp, expireTimer, profileKey) { @@ -682,7 +716,7 @@ textsecure.MessageSender = function(url, username, password, cdn_url) { this.sendRequestContactSyncMessage = sender.sendRequestContactSyncMessage .bind(sender); this.sendRequestConfigurationSyncMessage = sender.sendRequestConfigurationSyncMessage.bind(sender); this.sendMessageToNumber = sender.sendMessageToNumber .bind(sender); - this.closeSession = sender.closeSession .bind(sender); + this.resetSession = sender.resetSession .bind(sender); this.sendMessageToGroup = sender.sendMessageToGroup .bind(sender); this.createGroup = sender.createGroup .bind(sender); this.updateGroup = sender.updateGroup .bind(sender);