From 18ccda83ba74a669fbaec9a9dff029914519aa02 Mon Sep 17 00:00:00 2001 From: Evan Hahn <69474926+EvanHahn-Signal@users.noreply.github.com> Date: Mon, 5 Apr 2021 15:38:36 -0500 Subject: [PATCH] Use UUID-only sender certificate when applicable --- ts/models/conversations.ts | 71 ++++++++++++++++++++++++++++---- ts/textsecure/OutgoingMessage.ts | 22 +++------- ts/textsecure/SendMessage.ts | 2 +- 3 files changed, 70 insertions(+), 25 deletions(-) diff --git a/ts/models/conversations.ts b/ts/models/conversations.ts index d19304907b36..b91701a1261f 100644 --- a/ts/models/conversations.ts +++ b/ts/models/conversations.ts @@ -17,7 +17,12 @@ import { } from '../components/conversation/conversation-details/PendingInvites'; import { GroupV2Membership } from '../components/conversation/conversation-details/ConversationDetailsMembershipList'; import { CallMode, CallHistoryDetailsType } from '../types/Calling'; -import { CallbackResultType, GroupV2InfoType } from '../textsecure/SendMessage'; +import { + CallbackResultType, + GroupV2InfoType, + SendMetadataType, + SendOptionsType, +} from '../textsecure/SendMessage'; import { ConversationType, ConversationTypeType, @@ -26,6 +31,7 @@ import { ColorType } from '../types/Colors'; import { MessageModel } from './messages'; import { isMuted } from '../util/isMuted'; import { isConversationUnregistered } from '../util/isConversationUnregistered'; +import { assert } from '../util/assert'; import { missingCaseError } from '../util/missingCaseError'; import { sniffImageMimeType } from '../util/sniffImageMimeType'; import { MIMEType, IMAGE_WEBP } from '../types/MIME'; @@ -44,6 +50,11 @@ import { BodyRangesType } from '../types/Util'; import { getTextWithMentions } from '../util'; import { migrateColor } from '../util/migrateColor'; import { isNotNil } from '../util/isNotNil'; +import { + PhoneNumberSharingMode, + parsePhoneNumberSharingMode, +} from '../util/phoneNumberSharingMode'; +import { SerializedCertificateType } from '../metadata/SecretSessionCipher'; /* eslint-disable more/no-then */ window.Whisper = window.Whisper || {}; @@ -3544,19 +3555,17 @@ export class ConversationModel extends window.Backbone.Model< ); } - getSendOptions(options = {}): WhatIsThis { - const senderCertificate = window.storage.get('senderCertificate'); + getSendOptions(options = {}): SendOptionsType { const sendMetadata = this.getSendMetadata(options); return { - senderCertificate, sendMetadata, }; } getSendMetadata( options: { syncMessage?: string; disableMeCheck?: boolean } = {} - ): WhatIsThis | null { + ): SendMetadataType | undefined { const { syncMessage, disableMeCheck } = options; // START: this code has an Expiration date of ~2018/11/21 @@ -3566,7 +3575,7 @@ export class ConversationModel extends window.Backbone.Model< // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const me = window.ConversationController.get(myId)!; if (!disableMeCheck && me.get('sealedSender') === SEALED_SENDER.DISABLED) { - return null; + return undefined; } // END @@ -3583,16 +3592,19 @@ export class ConversationModel extends window.Backbone.Model< // We never send sync messages as sealed sender if (syncMessage && this.isMe()) { - return null; + return undefined; } const e164 = this.get('e164'); const uuid = this.get('uuid'); + const senderCertificate = this.getSenderCertificateForDirectConversation(); + // If we've never fetched user's profile, we default to what we have if (sealedSender === SEALED_SENDER.UNKNOWN) { const info = { accessKey: accessKey || arrayBufferToBase64(getRandomBytes(16)), + senderCertificate, }; return { ...(e164 ? { [e164]: info } : {}), @@ -3601,7 +3613,7 @@ export class ConversationModel extends window.Backbone.Model< } if (sealedSender === SEALED_SENDER.DISABLED) { - return null; + return undefined; } const info = { @@ -3609,6 +3621,7 @@ export class ConversationModel extends window.Backbone.Model< accessKey && sealedSender === SEALED_SENDER.ENABLED ? accessKey : arrayBufferToBase64(getRandomBytes(16)), + senderCertificate, }; return { @@ -3617,6 +3630,48 @@ export class ConversationModel extends window.Backbone.Model< }; } + private getSenderCertificateForDirectConversation(): + | undefined + | SerializedCertificateType { + if (!this.isPrivate()) { + throw new Error( + 'getSenderCertificateForDirectConversation should only be called for direct conversations' + ); + } + + const phoneNumberSharingMode = parsePhoneNumberSharingMode( + window.storage.get('phoneNumberSharingMode') + ); + + let storageKey: 'senderCertificate' | 'senderCertificateNoE164'; + switch (phoneNumberSharingMode) { + case PhoneNumberSharingMode.Everybody: + storageKey = 'senderCertificate'; + break; + case PhoneNumberSharingMode.ContactsOnly: { + const isInSystemContacts = Boolean(this.get('name')); + storageKey = isInSystemContacts + ? 'senderCertificate' + : 'senderCertificateNoE164'; + break; + } + case PhoneNumberSharingMode.Nobody: + storageKey = 'senderCertificateNoE164'; + break; + default: + throw missingCaseError(phoneNumberSharingMode); + } + + const result = window.storage.get(storageKey); + assert( + result, + `getSenderCertificateForDirectConversation: couldn't find a certificate stored in ${JSON.stringify( + storageKey + )}. Returning undefined` + ); + return result; + } + // Is this someone who is a contact, or are we sharing our profile with them? // Or is the person who added us to this group a contact or are we sharing profile // with them? diff --git a/ts/textsecure/OutgoingMessage.ts b/ts/textsecure/OutgoingMessage.ts index 5179002f6c79..524b0cc693ce 100644 --- a/ts/textsecure/OutgoingMessage.ts +++ b/ts/textsecure/OutgoingMessage.ts @@ -26,10 +26,7 @@ import { UnregisteredUserError, } from './Errors'; import { isValidNumber } from '../types/PhoneNumber'; -import { - SecretSessionCipher, - SerializedCertificateType, -} from '../metadata/SecretSessionCipher'; +import { SecretSessionCipher } from '../metadata/SecretSessionCipher'; type OutgoingMessageOptionsType = SendOptionsType & { online?: boolean; @@ -62,8 +59,6 @@ export default class OutgoingMessage { sendMetadata?: SendMetadataType; - senderCertificate?: SerializedCertificateType; - online?: boolean; constructor( @@ -96,9 +91,8 @@ export default class OutgoingMessage { this.failoverIdentifiers = []; this.unidentifiedDeliveries = []; - const { sendMetadata, senderCertificate, online } = options; + const { sendMetadata, online } = options; this.sendMetadata = sendMetadata; - this.senderCertificate = senderCertificate; this.online = online; } @@ -344,12 +338,8 @@ export default class OutgoingMessage { } = {}; const plaintext = this.getPlaintext(); - const { sendMetadata, senderCertificate } = this; - const info = - sendMetadata && sendMetadata[identifier] - ? sendMetadata[identifier] - : { accessKey: undefined }; - const { accessKey } = info; + const { sendMetadata } = this; + const { accessKey, senderCertificate } = sendMetadata?.[identifier] || {}; if (accessKey && !senderCertificate) { window.log.warn( @@ -442,8 +432,8 @@ export default class OutgoingMessage { } // This ensures that we don't hit this codepath the next time through - if (info) { - info.accessKey = undefined; + if (sendMetadata) { + delete sendMetadata[identifier]; } return this.doSendMessage(identifier, deviceIds, recurse); diff --git a/ts/textsecure/SendMessage.ts b/ts/textsecure/SendMessage.ts index a6c1f19185a8..77ab3106cec4 100644 --- a/ts/textsecure/SendMessage.ts +++ b/ts/textsecure/SendMessage.ts @@ -63,11 +63,11 @@ function stringToArrayBuffer(str: string): ArrayBuffer { export type SendMetadataType = { [identifier: string]: { accessKey: string; + senderCertificate?: SerializedCertificateType; }; }; export type SendOptionsType = { - senderCertificate?: SerializedCertificateType; sendMetadata?: SendMetadataType; online?: boolean; };