split doSendMessage in sub methods

pull/1084/head
Audric Ackermann 5 years ago
parent 2015ee7647
commit edbe79486c
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4

@ -293,46 +293,10 @@ OutgoingMessage.prototype = {
const bytes = new Uint8Array(websocketMessage.encode().toArrayBuffer()); const bytes = new Uint8Array(websocketMessage.encode().toArrayBuffer());
return bytes; return bytes;
}, },
doSendMessage(number, devicesPubKeys, recurse) {
const ciphers = {};
if (this.isPublic) {
return this.transmitMessage(
number,
this.message.dataMessage,
this.timestamp,
0 // ttl
)
.then(() => {
this.successfulNumbers[this.successfulNumbers.length] = number;
this.numberCompleted();
})
.catch(error => {
throw error;
});
}
this.numbers = devicesPubKeys;
return Promise.all( async buildMessage(devicePubKey) {
devicesPubKeys.map(async devicePubKey => { const updatedDevices = await this.getStaleDeviceIdsForNumber(devicePubKey);
// Session doesn't use the deviceId scheme, it's always 1. const keysFound = await this.getKeysForNumber(devicePubKey, updatedDevices);
// Instead, there are multiple device public keys.
const deviceId = 1;
const updatedDevices = await this.getStaleDeviceIdsForNumber(
devicePubKey
);
const keysFound = await this.getKeysForNumber(
devicePubKey,
updatedDevices
);
let enableFallBackEncryption = !keysFound;
const address = new libsignal.SignalProtocolAddress(
devicePubKey,
deviceId
);
const ourKey = textsecure.storage.user.getNumber();
const options = {};
let isMultiDeviceRequest = false; let isMultiDeviceRequest = false;
let thisDeviceMessageType = this.messageType; let thisDeviceMessageType = this.messageType;
@ -340,17 +304,12 @@ OutgoingMessage.prototype = {
thisDeviceMessageType !== 'pairing-request' && thisDeviceMessageType !== 'pairing-request' &&
thisDeviceMessageType !== 'friend-request' thisDeviceMessageType !== 'friend-request'
) { ) {
let conversation;
try { try {
conversation = ConversationController.get(devicePubKey); const conversation = ConversationController.get(devicePubKey);
} catch (e) {
// do nothing
}
if (conversation && !this.isGroup) { if (conversation && !this.isGroup) {
const isOurDevice = await conversation.isOurDevice(); const isOurDevice = await conversation.isOurDevice();
const isFriends = const isFriends =
conversation.isFriend() || conversation.isFriend() || conversation.hasReceivedFriendRequest();
conversation.hasReceivedFriendRequest();
// We should only send a friend request to our device if we don't have keys // We should only send a friend request to our device if we don't have keys
const shouldSendAutomatedFR = isOurDevice ? !keysFound : !isFriends; const shouldSendAutomatedFR = isOurDevice ? !keysFound : !isFriends;
if (shouldSendAutomatedFR) { if (shouldSendAutomatedFR) {
@ -375,33 +334,29 @@ OutgoingMessage.prototype = {
); );
} }
} }
} catch (e) {
// do nothing
}
} }
// Check if we need to attach the preKeys // Check if we need to attach the preKeys
let sessionCipher; const enableFallBackEncryption =
const isFriendRequest = thisDeviceMessageType === 'friend-request'; !keysFound || thisDeviceMessageType === 'friend-request';
enableFallBackEncryption =
enableFallBackEncryption || isFriendRequest || isMultiDeviceRequest;
const flags = this.message.dataMessage const flags = this.message.dataMessage
? this.message.dataMessage.get_flags() ? this.message.dataMessage.get_flags()
: null; : null;
// END_SESSION means Session reset message
const isEndSession = const isEndSession =
flags === textsecure.protobuf.DataMessage.Flags.END_SESSION; flags === textsecure.protobuf.DataMessage.Flags.END_SESSION;
const isSessionRequest = const isSessionRequest =
flags === textsecure.protobuf.DataMessage.Flags.SESSION_REQUEST; flags === textsecure.protobuf.DataMessage.Flags.SESSION_REQUEST;
const signalCipher = new libsignal.SessionCipher(
textsecure.storage.protocol,
address
);
if (enableFallBackEncryption || isEndSession) { if (enableFallBackEncryption || isEndSession) {
// Encrypt them with the fallback // Encrypt them with the fallback
const pkb = await libloki.storage.getPreKeyBundleForContact( const pkb = await libloki.storage.getPreKeyBundleForContact(devicePubKey);
devicePubKey this.message.preKeyBundleMessage = new textsecure.protobuf.PreKeyBundleMessage(
);
const preKeyBundleMessage = new textsecure.protobuf.PreKeyBundleMessage(
pkb pkb
); );
this.message.preKeyBundleMessage = preKeyBundleMessage;
window.log.info('attaching prekeys to outgoing message'); window.log.info('attaching prekeys to outgoing message');
} }
@ -420,26 +375,67 @@ OutgoingMessage.prototype = {
messageBuffer = this.message.toArrayBuffer(); messageBuffer = this.message.toArrayBuffer();
} }
if (enableFallBackEncryption) {
sessionCipher = new libloki.crypto.FallBackSessionCipher(address);
} else {
sessionCipher = signalCipher;
}
const plaintext = this.getPlaintext(messageBuffer); const plaintext = this.getPlaintext(messageBuffer);
// No limit on message keys if we're communicating with our other devices // No limit on message keys if we're communicating with our other devices
if (ourKey === number) { // FIXME options not used at all; if (ourPubkey === number) {
options.messageKeysLimit = false; // options.messageKeysLimit = false;
// }
const ttl = getTTLForType(thisDeviceMessageType);
const ourKey = textsecure.storage.user.getNumber();
return {
ttl,
ourKey,
sourceDevice: 1,
plaintext,
pubKey: devicePubKey,
isFriendRequest: enableFallBackEncryption,
isSessionRequest,
enableFallBackEncryption,
};
},
async encryptMessage(clearMessage) {
if (clearMessage === null) {
window.log.warn(
'clearMessage is null on encryptMessage... Returning null'
);
return null;
} }
const {
ttl,
ourKey,
sourceDevice,
plaintext,
pubKey,
isSessionRequest,
enableFallBackEncryption,
} = clearMessage;
// Session doesn't use the deviceId scheme, it's always 1.
// Instead, there are multiple device public keys.
const deviceId = 1;
let content; const address = new libsignal.SignalProtocolAddress(pubKey, deviceId);
let type;
let sessionCipher;
if (enableFallBackEncryption) {
sessionCipher = new libloki.crypto.FallBackSessionCipher(address);
} else {
sessionCipher = new libsignal.SessionCipher(
textsecure.storage.protocol,
address
);
}
let type;
let content;
if (window.lokiFeatureFlags.useSealedSender) { if (window.lokiFeatureFlags.useSealedSender) {
const secretSessionCipher = new window.Signal.Metadata.SecretSessionCipher( const secretSessionCipher = new window.Signal.Metadata.SecretSessionCipher(
textsecure.storage.protocol textsecure.storage.protocol
); );
ciphers[address.getDeviceId()] = secretSessionCipher; // ciphers[address.getDeviceId()] = secretSessionCipher;
const senderCert = new textsecure.protobuf.SenderCertificate(); const senderCert = new textsecure.protobuf.SenderCertificate();
@ -452,14 +448,12 @@ OutgoingMessage.prototype = {
plaintext, plaintext,
sessionCipher sessionCipher
); );
type = textsecure.protobuf.Envelope.Type.UNIDENTIFIED_SENDER; type = textsecure.protobuf.Envelope.Type.UNIDENTIFIED_SENDER;
content = window.Signal.Crypto.arrayBufferToBase64(ciphertext); content = window.Signal.Crypto.arrayBufferToBase64(ciphertext);
} else { } else {
// TODO: probably remove this branch once // TODO: probably remove this branch once
// mobile clients implement sealed sender // mobile clients implement sealed sender
ciphers[address.getDeviceId()] = sessionCipher; // ciphers[address.getDeviceId()] = sessionCipher;
const ciphertext = await sessionCipher.encrypt(plaintext); const ciphertext = await sessionCipher.encrypt(plaintext);
if (!enableFallBackEncryption) { if (!enableFallBackEncryption) {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
@ -467,27 +461,40 @@ OutgoingMessage.prototype = {
dcodeIO.ByteBuffer.wrap(ciphertext.body, 'binary').toArrayBuffer() dcodeIO.ByteBuffer.wrap(ciphertext.body, 'binary').toArrayBuffer()
); );
} }
// eslint-disable-next-line prefer-destructuring // eslint-disable-next-line prefer-destructuring
type = ciphertext.type; type = ciphertext.type;
content = ciphertext.body; content = ciphertext.body;
} }
const ttl = getTTLForType(thisDeviceMessageType);
return { return {
type, // FallBackSessionCipher sets this to FRIEND_REQUEST type, // FallBackSessionCipher sets this to FRIEND_REQUEST
ttl, ttl,
ourKey, ourKey,
sourceDevice: 1, sourceDevice,
content, content,
pubKey: devicePubKey, pubKey,
isFriendRequest: enableFallBackEncryption, isFriendRequest: enableFallBackEncryption,
isSessionRequest, isSessionRequest,
}; };
}) },
// Send a message to a public group
sendPublicMessage(number) {
return this.transmitMessage(
number,
this.message.dataMessage,
this.timestamp,
0 // ttl
) )
.then(async outgoingObjects => { .then(() => {
this.successfulNumbers[this.successfulNumbers.length] = number;
this.numberCompleted();
})
.catch(error => {
throw error;
});
},
// Send a message to a private group or a session chat (one to one)
async sendSessionMessage(outgoingObjects) {
// TODO: handle multiple devices/messages per transmit // TODO: handle multiple devices/messages per transmit
const promises = outgoingObjects.map(async outgoingObject => { const promises = outgoingObjects.map(async outgoingObject => {
if (!outgoingObject) { if (!outgoingObject) {
@ -500,9 +507,7 @@ OutgoingMessage.prototype = {
isSessionRequest, isSessionRequest,
} = outgoingObject; } = outgoingObject;
try { try {
const socketMessage = await this.wrapInWebsocketMessage( const socketMessage = await this.wrapInWebsocketMessage(outgoingObject);
outgoingObject
);
await this.transmitMessage( await this.transmitMessage(
destination, destination,
socketMessage, socketMessage,
@ -527,75 +532,33 @@ OutgoingMessage.prototype = {
// for which the transmission failed. // for which the transmission failed.
// ensure numberCompleted() will execute the callback // ensure numberCompleted() will execute the callback
this.numbersCompleted += this.numbersCompleted += this.errors.length + this.successfulNumbers.length;
this.errors.length + this.successfulNumbers.length;
// Absorb errors if message sent to at least 1 device // Absorb errors if message sent to at least 1 device
if (this.successfulNumbers.length > 0) { if (this.successfulNumbers.length > 0) {
this.errors = []; this.errors = [];
} }
this.numberCompleted(); this.numberCompleted();
}) },
async buildAndEncrypt(devicePubKey) {
const clearMessage = await this.buildMessage(devicePubKey);
return this.encryptMessage(clearMessage);
},
// eslint-disable-next-line no-unused-vars
doSendMessage(number, devicesPubKeys, recurse) {
if (this.isPublic) {
return this.sendPublicMessage(number);
}
this.numbers = devicesPubKeys;
return Promise.all(
devicesPubKeys.map(devicePubKey => this.buildAndEncrypt(devicePubKey))
)
.then(outgoingObjects => this.sendSessionMessage(outgoingObjects))
.catch(error => { .catch(error => {
// TODO(loki): handle http errors properly // TODO(loki): handle http errors properly
// - retry later if 400 // - retry later if 400
// - ignore if 409 (conflict) means the hash already exists // - ignore if 409 (conflict) means the hash already exists
throw error; throw error;
if (
error instanceof Error &&
error.name === 'HTTPError' &&
(error.code === 410 || error.code === 409)
) {
if (!recurse) {
return this.registerError(
number,
'Hit retry limit attempting to reload device list',
error
);
}
let p;
if (error.code === 409) {
p = this.removeDeviceIdsForNumber(
number,
error.response.extraDevices
);
} else {
p = Promise.all(
error.response.staleDevices.map(deviceId =>
ciphers[deviceId].closeOpenSessionForDevice(
new libsignal.SignalProtocolAddress(number, deviceId)
)
)
);
}
return p.then(() => {
const resetDevices =
error.code === 410
? error.response.staleDevices
: error.response.missingDevices;
return this.getKeysForNumber(number, resetDevices).then(
// We continue to retry as long as the error code was 409; the assumption is
// that we'll request new device info and the next request will succeed.
this.reloadDevicesAndSend(number, error.code === 409)
);
});
} else if (error.message === 'Identity key changed') {
// eslint-disable-next-line no-param-reassign
error.timestamp = this.timestamp;
// eslint-disable-next-line no-param-reassign
error.originalMessage = this.message.toArrayBuffer();
window.log.error(
'Got "key changed" error from encrypt - no identityKey for application layer',
number,
devicesPubKeys
);
throw error;
} else {
this.registerError(number, 'Failed to create or send message', error);
}
return null;
}); });
}, },

Loading…
Cancel
Save