Don't promote archived sessions, more logging on error
This commit is contained in:
parent
1098e59f87
commit
5369950c1d
|
@ -345,6 +345,7 @@
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const id = await normalizeEncodedAddress(encodedAddress);
|
const id = await normalizeEncodedAddress(encodedAddress);
|
||||||
|
window.log.info('loadSession', { encodedAddress, id });
|
||||||
const session = this.sessions[id];
|
const session = this.sessions[id];
|
||||||
|
|
||||||
if (session) {
|
if (session) {
|
||||||
|
@ -365,6 +366,7 @@
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const id = await normalizeEncodedAddress(encodedAddress);
|
const id = await normalizeEncodedAddress(encodedAddress);
|
||||||
|
window.log.info('storeSession', { encodedAddress, id });
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
id,
|
id,
|
||||||
|
@ -393,18 +395,34 @@
|
||||||
const sessions = allSessions.filter(
|
const sessions = allSessions.filter(
|
||||||
session => session.conversationId === id
|
session => session.conversationId === id
|
||||||
);
|
);
|
||||||
|
const openSessions = await Promise.all(
|
||||||
|
sessions.map(async session => {
|
||||||
|
const sessionCipher = new libsignal.SessionCipher(
|
||||||
|
textsecure.storage.protocol,
|
||||||
|
session.id
|
||||||
|
);
|
||||||
|
|
||||||
return _.pluck(sessions, 'deviceId');
|
const hasOpenSession = await sessionCipher.hasOpenSession();
|
||||||
} catch (e) {
|
if (hasOpenSession) {
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return openSessions.filter(Boolean).map(item => item.deviceId);
|
||||||
|
} catch (error) {
|
||||||
window.log.error(
|
window.log.error(
|
||||||
`could not get device ids for identifier ${identifier}`
|
`could not get device ids for identifier ${identifier}`,
|
||||||
|
error && error.stack ? error.stack : error
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
},
|
},
|
||||||
async removeSession(encodedAddress) {
|
async removeSession(encodedAddress) {
|
||||||
window.log.info('deleting session for ', encodedAddress);
|
window.log.info('removeSession: deleting session for', encodedAddress);
|
||||||
try {
|
try {
|
||||||
const id = await normalizeEncodedAddress(encodedAddress);
|
const id = await normalizeEncodedAddress(encodedAddress);
|
||||||
delete this.sessions[id];
|
delete this.sessions[id];
|
||||||
|
@ -418,6 +436,8 @@
|
||||||
throw new Error('Tried to remove sessions for undefined/null number');
|
throw new Error('Tried to remove sessions for undefined/null number');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.log.info('removeAllSessions: deleting sessions for', identifier);
|
||||||
|
|
||||||
const id = ConversationController.getConversationId(identifier);
|
const id = ConversationController.getConversationId(identifier);
|
||||||
|
|
||||||
const allSessions = Object.values(this.sessions);
|
const allSessions = Object.values(this.sessions);
|
||||||
|
@ -432,6 +452,11 @@
|
||||||
await window.Signal.Data.removeSessionsByConversation(identifier);
|
await window.Signal.Data.removeSessionsByConversation(identifier);
|
||||||
},
|
},
|
||||||
async archiveSiblingSessions(identifier) {
|
async archiveSiblingSessions(identifier) {
|
||||||
|
window.log.info(
|
||||||
|
'archiveSiblingSessions: archiving sibling sessions for',
|
||||||
|
identifier
|
||||||
|
);
|
||||||
|
|
||||||
const address = libsignal.SignalProtocolAddress.fromString(identifier);
|
const address = libsignal.SignalProtocolAddress.fromString(identifier);
|
||||||
|
|
||||||
const deviceIds = await this.getDeviceIds(address.getName());
|
const deviceIds = await this.getDeviceIds(address.getName());
|
||||||
|
@ -443,7 +468,10 @@
|
||||||
address.getName(),
|
address.getName(),
|
||||||
deviceId
|
deviceId
|
||||||
);
|
);
|
||||||
window.log.info('closing session for', sibling.toString());
|
window.log.info(
|
||||||
|
'archiveSiblingSessions: closing session for',
|
||||||
|
sibling.toString()
|
||||||
|
);
|
||||||
const sessionCipher = new libsignal.SessionCipher(
|
const sessionCipher = new libsignal.SessionCipher(
|
||||||
textsecure.storage.protocol,
|
textsecure.storage.protocol,
|
||||||
sibling
|
sibling
|
||||||
|
@ -453,6 +481,11 @@
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
async archiveAllSessions(identifier) {
|
async archiveAllSessions(identifier) {
|
||||||
|
window.log.info(
|
||||||
|
'archiveAllSessions: archiving all sessions for',
|
||||||
|
identifier
|
||||||
|
);
|
||||||
|
|
||||||
const deviceIds = await this.getDeviceIds(identifier);
|
const deviceIds = await this.getDeviceIds(identifier);
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
|
@ -461,7 +494,10 @@
|
||||||
identifier,
|
identifier,
|
||||||
deviceId
|
deviceId
|
||||||
);
|
);
|
||||||
window.log.info('closing session for', address.toString());
|
window.log.info(
|
||||||
|
'archiveAllSessions: closing session for',
|
||||||
|
address.toString()
|
||||||
|
);
|
||||||
const sessionCipher = new libsignal.SessionCipher(
|
const sessionCipher = new libsignal.SessionCipher(
|
||||||
textsecure.storage.protocol,
|
textsecure.storage.protocol,
|
||||||
address
|
address
|
||||||
|
|
|
@ -24840,7 +24840,14 @@ SessionCipher.prototype = {
|
||||||
if (sessionList.length === 0) {
|
if (sessionList.length === 0) {
|
||||||
var error = errors[0];
|
var error = errors[0];
|
||||||
if (!error) {
|
if (!error) {
|
||||||
error = new Error('decryptWithSessionList: list is empty, but no errors in array');
|
error = new Error('decryptWithSessionList: list is empty, but no errors in array');
|
||||||
|
}
|
||||||
|
if (errors.length > 1) {
|
||||||
|
errors.forEach((item, index) => {
|
||||||
|
var stackString = error && error.stack ? error.stack : error;
|
||||||
|
var extraString = error && error.extra ? JSON.stringify(error.extra) : '';
|
||||||
|
console.error(`decryptWithSessionList: Error at index ${index}: ${extraString} ${stackString}`);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
|
@ -24865,11 +24872,17 @@ SessionCipher.prototype = {
|
||||||
if (!record) {
|
if (!record) {
|
||||||
throw new Error("No record for device " + address);
|
throw new Error("No record for device " + address);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only used for printing out debug information when errors happen
|
||||||
|
var messageProto = buffer.slice(1, buffer.byteLength - 8);
|
||||||
|
var message = Internal.protobuf.WhisperMessage.decode(messageProto);
|
||||||
|
var byEphemeralKey = record.getSessionByRemoteEphemeralKey(util.toString(message.ephemeralKey));
|
||||||
|
|
||||||
var errors = [];
|
var errors = [];
|
||||||
return this.decryptWithSessionList(buffer, record.getSessions(), errors).then(function(result) {
|
return this.decryptWithSessionList(buffer, record.getSessions(), errors).then(function(result) {
|
||||||
return this.getRecord(address).then(function(record) {
|
return this.getRecord(address).then(function(record) {
|
||||||
var openSession = record.getOpenSession();
|
var openSession = record.getOpenSession();
|
||||||
if (!openSession || result.session.indexInfo.baseKey !== openSession.indexInfo.baseKey) {
|
if (!openSession) {
|
||||||
record.archiveCurrentState();
|
record.archiveCurrentState();
|
||||||
record.promoteState(result.session);
|
record.promoteState(result.session);
|
||||||
}
|
}
|
||||||
|
@ -24889,7 +24902,36 @@ SessionCipher.prototype = {
|
||||||
});
|
});
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
}.bind(this));
|
}.bind(this)).catch(function(error) {
|
||||||
|
try {
|
||||||
|
error.extra = error.extra || {};
|
||||||
|
error.extra.foundMatchingSession = Boolean(byEphemeralKey);
|
||||||
|
|
||||||
|
if (byEphemeralKey) {
|
||||||
|
var receivingChainInfo = {};
|
||||||
|
var entries = Object.entries(byEphemeralKey);
|
||||||
|
|
||||||
|
entries.forEach(([key, item]) => {
|
||||||
|
if (item && item.chainType === Internal.ChainType.RECEIVING && item.chainKey) {
|
||||||
|
var hexKey = dcodeIO.ByteBuffer.wrap(key, 'binary').toString('hex');
|
||||||
|
receivingChainInfo[hexKey] = {
|
||||||
|
counter: item.chainKey.counter,
|
||||||
|
key: item.chainKey.key ? dcodeIO.ByteBuffer.wrap(item.chainKey.key, 'binary').toString('hex') : null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
error.extra.receivingChainInfo = receivingChainInfo;
|
||||||
|
}
|
||||||
|
} catch (innerError) {
|
||||||
|
console.error(
|
||||||
|
'decryptWhisperMessage: Problem collecting extra information:',
|
||||||
|
innerError && innerError.stack ? innerError.stack : innerError
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
@ -24946,7 +24988,12 @@ SessionCipher.prototype = {
|
||||||
var remoteEphemeralKey = message.ephemeralKey.toArrayBuffer();
|
var remoteEphemeralKey = message.ephemeralKey.toArrayBuffer();
|
||||||
|
|
||||||
if (session === undefined) {
|
if (session === undefined) {
|
||||||
return Promise.reject(new Error("No session found to decrypt message from " + this.remoteAddress.toString()));
|
var error = new Error('No session found to decrypt message from ' + this.remoteAddress.toString());
|
||||||
|
error.extra = {
|
||||||
|
messageCounter: message.counter,
|
||||||
|
ratchetKey: message.ephemeralKey.toString('hex'),
|
||||||
|
};
|
||||||
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
if (session.indexInfo.closed != -1) {
|
if (session.indexInfo.closed != -1) {
|
||||||
console.log('decrypting message for closed session');
|
console.log('decrypting message for closed session');
|
||||||
|
@ -24961,7 +25008,7 @@ SessionCipher.prototype = {
|
||||||
return this.fillMessageKeys(chain, message.counter).then(function() {
|
return this.fillMessageKeys(chain, message.counter).then(function() {
|
||||||
var messageKey = chain.messageKeys[message.counter];
|
var messageKey = chain.messageKeys[message.counter];
|
||||||
if (messageKey === undefined) {
|
if (messageKey === undefined) {
|
||||||
var e = new Error("Message key not found. The counter was repeated or the key was not filled.");
|
var e = new Error(`Message key not found. Counter ${message.counter} was repeated or the key was not filled.`);
|
||||||
e.name = 'MessageCounterError';
|
e.name = 'MessageCounterError';
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
@ -24979,24 +25026,19 @@ SessionCipher.prototype = {
|
||||||
macInput[33*2] = (3 << 4) | 3;
|
macInput[33*2] = (3 << 4) | 3;
|
||||||
macInput.set(new Uint8Array(messageProto), 33*2 + 1);
|
macInput.set(new Uint8Array(messageProto), 33*2 + 1);
|
||||||
|
|
||||||
return Internal.verifyMAC(macInput.buffer, keys[1], mac, 8).catch(function(error) {
|
return Internal.verifyMAC(macInput.buffer, keys[1], mac, 8);
|
||||||
function logArrayBuffer(name, arrayBuffer) {
|
|
||||||
console.log('Bad MAC: ' + name + ' - truthy: ' + Boolean(arrayBuffer) +', length: ' + (arrayBuffer ? arrayBuffer.byteLength : 'NaN'));
|
|
||||||
}
|
|
||||||
|
|
||||||
logArrayBuffer('ourPubKey', ourPubKey);
|
|
||||||
logArrayBuffer('remoteIdentityKey', remoteIdentityKey);
|
|
||||||
logArrayBuffer('messageProto', messageProto);
|
|
||||||
logArrayBuffer('mac', mac);
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
});
|
|
||||||
}.bind(this)).then(function() {
|
}.bind(this)).then(function() {
|
||||||
return Internal.crypto.decrypt(keys[0], message.ciphertext.toArrayBuffer(), keys[2].slice(0, 16));
|
return Internal.crypto.decrypt(keys[0], message.ciphertext.toArrayBuffer(), keys[2].slice(0, 16));
|
||||||
});
|
});
|
||||||
}.bind(this)).then(function(plaintext) {
|
}.bind(this)).then(function(plaintext) {
|
||||||
delete session.pendingPreKey;
|
delete session.pendingPreKey;
|
||||||
return plaintext;
|
return plaintext;
|
||||||
|
}).catch(function(error) {
|
||||||
|
error.extra = {
|
||||||
|
messageCounter: message.counter,
|
||||||
|
ratchetKey: message.ephemeralKey.toString('hex'),
|
||||||
|
};
|
||||||
|
throw error;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
fillMessageKeys: function(chain, counter) {
|
fillMessageKeys: function(chain, counter) {
|
||||||
|
|
|
@ -975,17 +975,39 @@ describe('SignalProtocolStore', () => {
|
||||||
});
|
});
|
||||||
describe('getDeviceIds', () => {
|
describe('getDeviceIds', () => {
|
||||||
it('returns deviceIds for a number', async () => {
|
it('returns deviceIds for a number', async () => {
|
||||||
const testRecord = 'an opaque string';
|
const openRecord = JSON.stringify({
|
||||||
const devices = [1, 2, 3, 10].map(deviceId => {
|
version: 'v1',
|
||||||
|
sessions: {
|
||||||
|
ephemeralKey: {
|
||||||
|
registrationId: 25,
|
||||||
|
indexInfo: {
|
||||||
|
closed: -1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const openDevices = [1, 2, 3, 10].map(deviceId => {
|
||||||
return [number, deviceId].join('.');
|
return [number, deviceId].join('.');
|
||||||
});
|
});
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
devices.map(async encodedNumber => {
|
openDevices.map(async encodedNumber => {
|
||||||
await store.storeSession(encodedNumber, testRecord + encodedNumber);
|
await store.storeSession(encodedNumber, openRecord);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const closedRecord = JSON.stringify({
|
||||||
|
version: 'v1',
|
||||||
|
sessions: {
|
||||||
|
ephemeralKey: {
|
||||||
|
registrationId: 24,
|
||||||
|
indexInfo: {
|
||||||
|
closed: Date.now(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await store.storeSession([number, 11].join('.'), closedRecord);
|
||||||
|
|
||||||
const deviceIds = await store.getDeviceIds(number);
|
const deviceIds = await store.getDeviceIds(number);
|
||||||
assert.sameMembers(deviceIds, [1, 2, 3, 10]);
|
assert.sameMembers(deviceIds, [1, 2, 3, 10]);
|
||||||
});
|
});
|
||||||
|
|
|
@ -775,6 +775,7 @@ class MessageReceiverInner extends EventTarget {
|
||||||
return promise.catch(error => {
|
return promise.catch(error => {
|
||||||
window.log.error(
|
window.log.error(
|
||||||
`queueDecryptedEnvelope error handling envelope ${id}:`,
|
`queueDecryptedEnvelope error handling envelope ${id}:`,
|
||||||
|
error && error.extra ? JSON.stringify(error.extra) : '',
|
||||||
error && error.stack ? error.stack : error
|
error && error.stack ? error.stack : error
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -796,6 +797,7 @@ class MessageReceiverInner extends EventTarget {
|
||||||
'queueEnvelope error handling envelope',
|
'queueEnvelope error handling envelope',
|
||||||
this.getEnvelopeId(envelope),
|
this.getEnvelopeId(envelope),
|
||||||
':',
|
':',
|
||||||
|
error && error.extra ? JSON.stringify(error.extra) : '',
|
||||||
error && error.stack ? error.stack : error,
|
error && error.stack ? error.stack : error,
|
||||||
];
|
];
|
||||||
if (error.warn) {
|
if (error.warn) {
|
||||||
|
@ -2044,8 +2046,11 @@ class MessageReceiverInner extends EventTarget {
|
||||||
address
|
address
|
||||||
);
|
);
|
||||||
|
|
||||||
window.log.info('deleting sessions for', address.toString());
|
window.log.info(
|
||||||
return sessionCipher.deleteAllSessionsForDevice();
|
'handleEndSession: closing sessions for',
|
||||||
|
address.toString()
|
||||||
|
);
|
||||||
|
return sessionCipher.closeOpenSessionForDevice();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -589,14 +589,19 @@ export default class OutgoingMessage {
|
||||||
identifier: string,
|
identifier: string,
|
||||||
deviceIdsToRemove: Array<number>
|
deviceIdsToRemove: Array<number>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
let promise = Promise.resolve();
|
await Promise.all(
|
||||||
for (const j in deviceIdsToRemove) {
|
deviceIdsToRemove.map(async deviceId => {
|
||||||
promise = promise.then(async () => {
|
const address = new window.libsignal.SignalProtocolAddress(
|
||||||
const encodedAddress = `${identifier}.${deviceIdsToRemove[j]}`;
|
identifier,
|
||||||
return window.textsecure.storage.protocol.removeSession(encodedAddress);
|
deviceId
|
||||||
});
|
);
|
||||||
}
|
const sessionCipher = new window.libsignal.SessionCipher(
|
||||||
return promise;
|
window.textsecure.storage.protocol,
|
||||||
|
address
|
||||||
|
);
|
||||||
|
await sessionCipher.closeOpenSessionForDevice();
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendToIdentifier(providedIdentifier: string): Promise<void> {
|
async sendToIdentifier(providedIdentifier: string): Promise<void> {
|
||||||
|
|
|
@ -1574,7 +1574,7 @@ export default class MessageSender {
|
||||||
): Promise<
|
): Promise<
|
||||||
CallbackResultType | void | Array<CallbackResultType | void | Array<void>>
|
CallbackResultType | void | Array<CallbackResultType | void | Array<void>>
|
||||||
> {
|
> {
|
||||||
window.log.info('resetting secure session');
|
window.log.info('resetSession: start');
|
||||||
const silent = false;
|
const silent = false;
|
||||||
const proto = new window.textsecure.protobuf.DataMessage();
|
const proto = new window.textsecure.protobuf.DataMessage();
|
||||||
proto.body = 'TERMINATE';
|
proto.body = 'TERMINATE';
|
||||||
|
@ -1587,7 +1587,7 @@ export default class MessageSender {
|
||||||
window.log.error(prefix, error && error.stack ? error.stack : error);
|
window.log.error(prefix, error && error.stack ? error.stack : error);
|
||||||
throw error;
|
throw error;
|
||||||
};
|
};
|
||||||
const deleteAllSessions = async (targetIdentifier: string) =>
|
const closeAllSessions = async (targetIdentifier: string) =>
|
||||||
window.textsecure.storage.protocol
|
window.textsecure.storage.protocol
|
||||||
.getDeviceIds(targetIdentifier)
|
.getDeviceIds(targetIdentifier)
|
||||||
.then(async deviceIds =>
|
.then(async deviceIds =>
|
||||||
|
@ -1597,21 +1597,24 @@ export default class MessageSender {
|
||||||
targetIdentifier,
|
targetIdentifier,
|
||||||
deviceId
|
deviceId
|
||||||
);
|
);
|
||||||
window.log.info('deleting sessions for', address.toString());
|
window.log.info(
|
||||||
|
'resetSession: closing sessions for',
|
||||||
|
address.toString()
|
||||||
|
);
|
||||||
const sessionCipher = new window.libsignal.SessionCipher(
|
const sessionCipher = new window.libsignal.SessionCipher(
|
||||||
window.textsecure.storage.protocol,
|
window.textsecure.storage.protocol,
|
||||||
address
|
address
|
||||||
);
|
);
|
||||||
return sessionCipher.deleteAllSessionsForDevice();
|
return sessionCipher.closeOpenSessionForDevice();
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const sendToContactPromise = deleteAllSessions(identifier)
|
const sendToContactPromise = closeAllSessions(identifier)
|
||||||
.catch(logError('resetSession/deleteAllSessions1 error:'))
|
.catch(logError('resetSession/closeAllSessions1 error:'))
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
window.log.info(
|
window.log.info(
|
||||||
'finished closing local sessions, now sending to contact'
|
'resetSession: finished closing local sessions, now sending to contact'
|
||||||
);
|
);
|
||||||
return this.sendIndividualProto(
|
return this.sendIndividualProto(
|
||||||
identifier,
|
identifier,
|
||||||
|
@ -1622,8 +1625,8 @@ export default class MessageSender {
|
||||||
).catch(logError('resetSession/sendToContact error:'));
|
).catch(logError('resetSession/sendToContact error:'));
|
||||||
})
|
})
|
||||||
.then(async () =>
|
.then(async () =>
|
||||||
deleteAllSessions(identifier).catch(
|
closeAllSessions(identifier).catch(
|
||||||
logError('resetSession/deleteAllSessions2 error:')
|
logError('resetSession/closeAllSessions2 error:')
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -326,7 +326,7 @@
|
||||||
"rule": "jQuery-load(",
|
"rule": "jQuery-load(",
|
||||||
"path": "js/signal_protocol_store.js",
|
"path": "js/signal_protocol_store.js",
|
||||||
"line": " await ConversationController.load();",
|
"line": " await ConversationController.load();",
|
||||||
"lineNumber": 983,
|
"lineNumber": 1019,
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2020-06-12T14:20:09.936Z"
|
"updated": "2020-06-12T14:20:09.936Z"
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue