Refine sealed sender logic
This commit is contained in:
parent
f11dd18536
commit
e2e0e4c96b
3 changed files with 156 additions and 98 deletions
|
@ -1121,9 +1121,11 @@
|
||||||
|
|
||||||
// Sent:
|
// Sent:
|
||||||
async function handleMessageSentProfileUpdate({
|
async function handleMessageSentProfileUpdate({
|
||||||
|
data,
|
||||||
confirm,
|
confirm,
|
||||||
messageDescriptor,
|
messageDescriptor,
|
||||||
}) {
|
}) {
|
||||||
|
// First set profileSharing = true for the conversation we sent to
|
||||||
const { id, type } = messageDescriptor;
|
const { id, type } = messageDescriptor;
|
||||||
const conversation = await ConversationController.getOrCreateAndWait(
|
const conversation = await ConversationController.getOrCreateAndWait(
|
||||||
id,
|
id,
|
||||||
|
@ -1135,6 +1137,14 @@
|
||||||
Conversation: Whisper.Conversation,
|
Conversation: Whisper.Conversation,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Then we update our own profileKey if it's different from what we have
|
||||||
|
const ourNumber = textsecure.storage.user.getNumber();
|
||||||
|
const profileKey = data.message.profileKey.toString('base64');
|
||||||
|
const me = await ConversationController.getOrCreate(ourNumber, 'private');
|
||||||
|
|
||||||
|
// Will do the save for us if needed
|
||||||
|
await me.setProfileKey(profileKey);
|
||||||
|
|
||||||
return confirm();
|
return confirm();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,13 @@
|
||||||
|
|
||||||
window.Whisper = window.Whisper || {};
|
window.Whisper = window.Whisper || {};
|
||||||
|
|
||||||
|
const SEALED_SENDER = {
|
||||||
|
UNKNOWN: 0,
|
||||||
|
ENABLED: 1,
|
||||||
|
DISABLED: 2,
|
||||||
|
UNRESTRICTED: 3,
|
||||||
|
};
|
||||||
|
|
||||||
const { Util } = window.Signal;
|
const { Util } = window.Signal;
|
||||||
const {
|
const {
|
||||||
Conversation,
|
Conversation,
|
||||||
|
@ -113,6 +120,14 @@
|
||||||
this.on('read', this.updateAndMerge);
|
this.on('read', this.updateAndMerge);
|
||||||
this.on('expiration-change', this.updateAndMerge);
|
this.on('expiration-change', this.updateAndMerge);
|
||||||
this.on('expired', this.onExpired);
|
this.on('expired', this.onExpired);
|
||||||
|
|
||||||
|
const sealedSender = this.get('sealedSender');
|
||||||
|
if (sealedSender === undefined) {
|
||||||
|
this.set({ sealedSender: SEALED_SENDER.UNKNOWN });
|
||||||
|
}
|
||||||
|
this.unset('unidentifiedDelivery');
|
||||||
|
this.unset('unidentifiedDeliveryUnrestricted');
|
||||||
|
this.unset('hasFetchedProfile');
|
||||||
},
|
},
|
||||||
|
|
||||||
isMe() {
|
isMe() {
|
||||||
|
@ -783,38 +798,65 @@
|
||||||
return promise.then(
|
return promise.then(
|
||||||
async result => {
|
async result => {
|
||||||
// success
|
// success
|
||||||
if (
|
if (result) {
|
||||||
result &&
|
await this.handleMessageSendResult(
|
||||||
result.failoverNumbers &&
|
result.failoverNumbers,
|
||||||
result.failoverNumbers.length
|
result.unidentifiedDeliveries
|
||||||
) {
|
);
|
||||||
await this.handleFailover(result.failoverNumbers);
|
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
async result => {
|
async result => {
|
||||||
// failure
|
// failure
|
||||||
if (
|
if (result) {
|
||||||
result &&
|
await this.handleMessageSendResult(
|
||||||
result.failoverNumbers &&
|
result.failoverNumbers,
|
||||||
result.failoverNumbers.length
|
result.unidentifiedDeliveries
|
||||||
) {
|
);
|
||||||
await this.handleFailover(result.failoverNumbers);
|
|
||||||
}
|
}
|
||||||
throw result;
|
throw result;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
handleFailover(numberArray) {
|
async handleMessageSendResult(failoverNumbers, unidentifiedDeliveries) {
|
||||||
return Promise.all(
|
await Promise.all(
|
||||||
(numberArray || []).map(async number => {
|
(failoverNumbers || []).map(async number => {
|
||||||
const conversation = ConversationController.get(number);
|
const conversation = ConversationController.get(number);
|
||||||
if (conversation && conversation.get('unidentifiedDelivery')) {
|
|
||||||
window.log.info(
|
if (
|
||||||
`Marking unidentifiedDelivery false for conversation ${conversation.idForLogging()}`
|
conversation &&
|
||||||
|
conversation.get('sealedSender') !== SEALED_SENDER.DISABLED
|
||||||
|
) {
|
||||||
|
conversation.set({
|
||||||
|
sealedSender: SEALED_SENDER.DISABLED,
|
||||||
|
});
|
||||||
|
await window.Signal.Data.updateConversation(
|
||||||
|
conversation.id,
|
||||||
|
conversation.attributes,
|
||||||
|
{ Conversation: Whisper.Conversation }
|
||||||
);
|
);
|
||||||
conversation.set({ unidentifiedDelivery: false });
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
(unidentifiedDeliveries || []).map(async number => {
|
||||||
|
const conversation = ConversationController.get(number);
|
||||||
|
|
||||||
|
if (
|
||||||
|
conversation &&
|
||||||
|
conversation.get('sealedSender') === SEALED_SENDER.UNKNOWN
|
||||||
|
) {
|
||||||
|
if (conversation.get('accessKey')) {
|
||||||
|
conversation.set({
|
||||||
|
sealedSender: SEALED_SENDER.ENABLED,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
conversation.set({
|
||||||
|
sealedSender: SEALED_SENDER.UNRESTRICTED,
|
||||||
|
});
|
||||||
|
}
|
||||||
await window.Signal.Data.updateConversation(
|
await window.Signal.Data.updateConversation(
|
||||||
conversation.id,
|
conversation.id,
|
||||||
conversation.attributes,
|
conversation.attributes,
|
||||||
|
@ -835,42 +877,54 @@
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
getNumberInfo() {
|
getNumberInfo({ disableMeCheck } = {}) {
|
||||||
const UD = 'unidentifiedDelivery';
|
|
||||||
const UNRESTRICTED_UD = 'unidentifiedDeliveryUnrestricted';
|
|
||||||
|
|
||||||
// We don't want to enable unidentified delivery for send unless it is
|
// We don't want to enable unidentified delivery for send unless it is
|
||||||
// also enabled for our own account.
|
// also enabled for our own account.
|
||||||
const me = ConversationController.getOrCreate(this.ourNumber, 'private');
|
const me = ConversationController.getOrCreate(this.ourNumber, 'private');
|
||||||
if (!me.get(UD) && !me.get(UNRESTRICTED_UD)) {
|
if (
|
||||||
|
!disableMeCheck &&
|
||||||
|
me.get('sealedSender') === SEALED_SENDER.DISABLED
|
||||||
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isPrivate()) {
|
if (!this.isPrivate()) {
|
||||||
const accessKey = this.get('accessKey');
|
const infoArray = this.contactCollection.map(conversation =>
|
||||||
const unidentifiedDelivery = this.get(UD);
|
conversation.getNumberInfo({ disableMeCheck })
|
||||||
const unrestricted = this.get(UNRESTRICTED_UD);
|
);
|
||||||
|
return Object.assign({}, ...infoArray);
|
||||||
|
}
|
||||||
|
|
||||||
if (!unidentifiedDelivery && !unrestricted) {
|
const accessKey = this.get('accessKey');
|
||||||
return null;
|
const sealedSender = this.get('sealedSender');
|
||||||
}
|
|
||||||
|
|
||||||
|
// If we've never fetched user's profile, we default to what we have
|
||||||
|
if (sealedSender === SEALED_SENDER.UNKNOWN) {
|
||||||
return {
|
return {
|
||||||
[this.id]: {
|
[this.id]: {
|
||||||
accessKey:
|
accessKey:
|
||||||
accessKey && !unrestricted
|
accessKey ||
|
||||||
? accessKey
|
window.Signal.Crypto.arrayBufferToBase64(
|
||||||
: window.Signal.Crypto.arrayBufferToBase64(
|
window.Signal.Crypto.getRandomBytes(16)
|
||||||
window.Signal.Crypto.getRandomBytes(16)
|
),
|
||||||
),
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const infoArray = this.contactCollection.map(conversation =>
|
if (sealedSender === SEALED_SENDER.DISABLED) {
|
||||||
conversation.getNumberInfo()
|
return null;
|
||||||
);
|
}
|
||||||
return Object.assign({}, ...infoArray);
|
|
||||||
|
return {
|
||||||
|
[this.id]: {
|
||||||
|
accessKey:
|
||||||
|
accessKey && sealedSender === SEALED_SENDER.ENABLED
|
||||||
|
? accessKey
|
||||||
|
: window.Signal.Crypto.arrayBufferToBase64(
|
||||||
|
window.Signal.Crypto.getRandomBytes(16)
|
||||||
|
),
|
||||||
|
},
|
||||||
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
async updateLastMessage() {
|
async updateLastMessage() {
|
||||||
|
@ -1237,37 +1291,19 @@
|
||||||
c.changed = {};
|
c.changed = {};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (c.get('profileKey') && !c.get('accessKey')) {
|
await c.deriveAccessKeyIfNeeded();
|
||||||
const profileKeyBuffer = window.Signal.Crypto.base64ToArrayBuffer(
|
const numberInfo = c.getNumberInfo({ disableMeCheck: true }) || {};
|
||||||
c.get('profileKey')
|
const getInfo = numberInfo[c.id] || {};
|
||||||
);
|
|
||||||
const buffer = await window.Signal.Crypto.deriveAccessKey(
|
|
||||||
profileKeyBuffer
|
|
||||||
);
|
|
||||||
c.set({
|
|
||||||
accessKey: window.Signal.Crypto.arrayBufferToBase64(buffer),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const firstProfileFetch = !c.get('hasFetchedProfile');
|
|
||||||
const accessKey = c.get('accessKey');
|
|
||||||
|
|
||||||
let profile;
|
let profile;
|
||||||
if (c.get('unidentifiedDelivery') || firstProfileFetch) {
|
if (getInfo.accessKey) {
|
||||||
try {
|
try {
|
||||||
profile = await textsecure.messaging.getProfile(id, {
|
profile = await textsecure.messaging.getProfile(id, {
|
||||||
accessKey:
|
accessKey: getInfo.accessKey,
|
||||||
accessKey ||
|
|
||||||
window.Signal.Crypto.arrayBufferToBase64(
|
|
||||||
window.window.Signal.Crypto.getRandomBytes(16)
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.code === 401 || error.code === 403) {
|
if (error.code === 401 || error.code === 403) {
|
||||||
c.set({
|
c.set({ sealedSender: SEALED_SENDER.DISABLED });
|
||||||
unidentifiedDelivery: false,
|
|
||||||
unidentifiedDeliveryUnrestricted: false,
|
|
||||||
});
|
|
||||||
profile = await textsecure.messaging.getProfile(id);
|
profile = await textsecure.messaging.getProfile(id);
|
||||||
} else {
|
} else {
|
||||||
throw error;
|
throw error;
|
||||||
|
@ -1297,17 +1333,13 @@
|
||||||
await sessionCipher.closeOpenSessionForDevice();
|
await sessionCipher.closeOpenSessionForDevice();
|
||||||
}
|
}
|
||||||
|
|
||||||
c.set({
|
const accessKey = c.get('accessKey');
|
||||||
hasFetchedProfile: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
profile.unrestrictedUnidentifiedAccess &&
|
profile.unrestrictedUnidentifiedAccess &&
|
||||||
profile.unidentifiedAccess
|
profile.unidentifiedAccess
|
||||||
) {
|
) {
|
||||||
c.set({
|
c.set({
|
||||||
unidentifiedDelivery: true,
|
sealedSender: SEALED_SENDER.UNRESTRICTED,
|
||||||
unidentifiedDeliveryUnrestricted: true,
|
|
||||||
});
|
});
|
||||||
} else if (accessKey && profile.unidentifiedAccess) {
|
} else if (accessKey && profile.unidentifiedAccess) {
|
||||||
const haveCorrectKey = await window.Signal.Crypto.verifyAccessKey(
|
const haveCorrectKey = await window.Signal.Crypto.verifyAccessKey(
|
||||||
|
@ -1315,17 +1347,18 @@
|
||||||
window.Signal.Crypto.base64ToArrayBuffer(profile.unidentifiedAccess)
|
window.Signal.Crypto.base64ToArrayBuffer(profile.unidentifiedAccess)
|
||||||
);
|
);
|
||||||
|
|
||||||
window.log.info(
|
if (haveCorrectKey) {
|
||||||
`Setting unidentifiedDelivery to ${haveCorrectKey} for conversation ${c.idForLogging()}`
|
c.set({
|
||||||
);
|
sealedSender: SEALED_SENDER.ENABLED,
|
||||||
c.set({
|
});
|
||||||
unidentifiedDelivery: haveCorrectKey,
|
} else {
|
||||||
unidentifiedDeliveryUnrestricted: false,
|
c.set({
|
||||||
});
|
sealedSender: SEALED_SENDER.DISABLED,
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
c.set({
|
c.set({
|
||||||
unidentifiedDelivery: false,
|
sealedSender: SEALED_SENDER.DISABLED,
|
||||||
unidentifiedDeliveryUnrestricted: false,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1406,17 +1439,13 @@
|
||||||
async setProfileKey(profileKey) {
|
async setProfileKey(profileKey) {
|
||||||
// profileKey is a string so we can compare it directly
|
// profileKey is a string so we can compare it directly
|
||||||
if (this.get('profileKey') !== profileKey) {
|
if (this.get('profileKey') !== profileKey) {
|
||||||
const profileKeyBuffer = window.Signal.Crypto.base64ToArrayBuffer(
|
this.set({
|
||||||
profileKey
|
profileKey,
|
||||||
);
|
accessKey: null,
|
||||||
const accessKeyBuffer = await window.Signal.Crypto.deriveAccessKey(
|
sealedSender: SEALED_SENDER.UNKNOWN,
|
||||||
profileKeyBuffer
|
});
|
||||||
);
|
|
||||||
const accessKey = window.Signal.Crypto.arrayBufferToBase64(
|
|
||||||
accessKeyBuffer
|
|
||||||
);
|
|
||||||
|
|
||||||
this.set({ profileKey, accessKey });
|
await this.deriveAccessKeyIfNeeded();
|
||||||
|
|
||||||
await window.Signal.Data.updateConversation(this.id, this.attributes, {
|
await window.Signal.Data.updateConversation(this.id, this.attributes, {
|
||||||
Conversation: Whisper.Conversation,
|
Conversation: Whisper.Conversation,
|
||||||
|
@ -1424,6 +1453,27 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async deriveAccessKeyIfNeeded() {
|
||||||
|
const profileKey = this.get('profileKey');
|
||||||
|
if (!profileKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.get('accessKey')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const profileKeyBuffer = window.Signal.Crypto.base64ToArrayBuffer(
|
||||||
|
profileKey
|
||||||
|
);
|
||||||
|
const accessKeyBuffer = await window.Signal.Crypto.deriveAccessKey(
|
||||||
|
profileKeyBuffer
|
||||||
|
);
|
||||||
|
const accessKey = window.Signal.Crypto.arrayBufferToBase64(
|
||||||
|
accessKeyBuffer
|
||||||
|
);
|
||||||
|
this.set({ accessKey });
|
||||||
|
},
|
||||||
|
|
||||||
async upgradeMessages(messages) {
|
async upgradeMessages(messages) {
|
||||||
for (let max = messages.length, i = 0; i < max; i += 1) {
|
for (let max = messages.length, i = 0; i < max; i += 1) {
|
||||||
const message = messages.at(i);
|
const message = messages.at(i);
|
||||||
|
|
|
@ -66,7 +66,7 @@ OutgoingMessage.prototype = {
|
||||||
this.errors[this.errors.length] = error;
|
this.errors[this.errors.length] = error;
|
||||||
this.numberCompleted();
|
this.numberCompleted();
|
||||||
},
|
},
|
||||||
reloadDevicesAndSend(number, recurse, failover) {
|
reloadDevicesAndSend(number, recurse) {
|
||||||
return () =>
|
return () =>
|
||||||
textsecure.storage.protocol.getDeviceIds(number).then(deviceIds => {
|
textsecure.storage.protocol.getDeviceIds(number).then(deviceIds => {
|
||||||
if (deviceIds.length === 0) {
|
if (deviceIds.length === 0) {
|
||||||
|
@ -76,7 +76,7 @@ OutgoingMessage.prototype = {
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return this.doSendMessage(number, deviceIds, recurse, failover);
|
return this.doSendMessage(number, deviceIds, recurse);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -245,7 +245,7 @@ OutgoingMessage.prototype = {
|
||||||
return this.plaintext;
|
return this.plaintext;
|
||||||
},
|
},
|
||||||
|
|
||||||
doSendMessage(number, deviceIds, recurse, failover) {
|
doSendMessage(number, deviceIds, recurse) {
|
||||||
const ciphers = {};
|
const ciphers = {};
|
||||||
const plaintext = this.getPlaintext();
|
const plaintext = this.getPlaintext();
|
||||||
|
|
||||||
|
@ -261,8 +261,7 @@ OutgoingMessage.prototype = {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If failover is true, we don't send an unidentified sender message
|
const sealedSender = Boolean(accessKey && senderCertificate);
|
||||||
const sealedSender = Boolean(!failover && accessKey && senderCertificate);
|
|
||||||
|
|
||||||
// We don't send to ourselves if unless sealedSender is enabled
|
// We don't send to ourselves if unless sealedSender is enabled
|
||||||
const ourNumber = textsecure.storage.user.getNumber();
|
const ourNumber = textsecure.storage.user.getNumber();
|
||||||
|
@ -288,7 +287,6 @@ OutgoingMessage.prototype = {
|
||||||
options.messageKeysLimit = false;
|
options.messageKeysLimit = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If failover is true, we don't send an unidentified sender message
|
|
||||||
if (sealedSender) {
|
if (sealedSender) {
|
||||||
const secretSessionCipher = new window.Signal.Metadata.SecretSessionCipher(
|
const secretSessionCipher = new window.Signal.Metadata.SecretSessionCipher(
|
||||||
textsecure.storage.protocol
|
textsecure.storage.protocol
|
||||||
|
@ -397,9 +395,9 @@ OutgoingMessage.prototype = {
|
||||||
? error.response.staleDevices
|
? error.response.staleDevices
|
||||||
: error.response.missingDevices;
|
: error.response.missingDevices;
|
||||||
return this.getKeysForNumber(number, resetDevices).then(
|
return this.getKeysForNumber(number, resetDevices).then(
|
||||||
// For now, we we won't retry unidentified delivery if we get here; new
|
// We continue to retry as long as the error code was 409; the assumption is
|
||||||
// devices could have been added which don't support it.
|
// that we'll request new device info and the next request will succeed.
|
||||||
this.reloadDevicesAndSend(number, error.code === 409, true)
|
this.reloadDevicesAndSend(number, error.code === 409)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
} else if (error.message === 'Identity key changed') {
|
} else if (error.message === 'Identity key changed') {
|
||||||
|
|
Loading…
Add table
Reference in a new issue