diff --git a/ts/services/backups/import.ts b/ts/services/backups/import.ts index c47dbb516b..ab4a99785e 100644 --- a/ts/services/backups/import.ts +++ b/ts/services/backups/import.ts @@ -47,6 +47,7 @@ import type { CustomColorType, CustomColorDataType, } from '../../types/Colors'; +import { SEALED_SENDER } from '../../types/SealedSender'; import type { ConversationAttributesType, CustomError, @@ -81,7 +82,7 @@ import { ReadStatus } from '../../messages/MessageReadStatus'; import { SendStatus } from '../../messages/MessageSendState'; import type { SendStateByConversationId } from '../../messages/MessageSendState'; import { SeenStatus } from '../../MessageSeenStatus'; -import { constantTimeEqual } from '../../Crypto'; +import { constantTimeEqual, deriveAccessKey } from '../../Crypto'; import * as Bytes from '../../Bytes'; import { BACKUP_VERSION, WALLPAPER_TO_BUBBLE_COLOR } from './constants'; import { UnsupportedBackupVersion } from './errors'; @@ -945,6 +946,10 @@ export class BackupImportStream extends Writable { profileKey: contact.profileKey ? Bytes.toBase64(contact.profileKey) : undefined, + accessKey: contact.profileKey + ? Bytes.toBase64(deriveAccessKey(contact.profileKey)) + : undefined, + sealedSender: SEALED_SENDER.UNKNOWN, profileSharing: contact.profileSharing === true, profileName: dropNull(contact.profileGivenName), profileFamilyName: dropNull(contact.profileFamilyName), diff --git a/ts/textsecure/WebAPI.ts b/ts/textsecure/WebAPI.ts index 5d72a22a4d..815ba744c7 100644 --- a/ts/textsecure/WebAPI.ts +++ b/ts/textsecure/WebAPI.ts @@ -36,6 +36,7 @@ import { createProxyAgent } from '../util/createProxyAgent'; import type { ProxyAgent } from '../util/createProxyAgent'; import type { FetchFunctionType } from '../util/uploads/tusProtocol'; import { VerificationTransport } from '../types/VerificationTransport'; +import { ZERO_ACCESS_KEY } from '../types/SealedSender'; import { toLogFormat } from '../types/errors'; import { isPackIdValid, redactPackId } from '../types/Stickers'; import type { @@ -362,7 +363,20 @@ async function _promiseAjax( const logType = socketManager ? '(WS)' : '(REST)'; const redactedURL = options.redactUrl ? options.redactUrl(url) : url; - const unauthLabel = options.unauthenticated ? ' (unauth)' : ''; + const { accessKey, basicAuth, groupSendToken, unauthenticated } = options; + + let unauthLabel = ''; + if (options.unauthenticated) { + if (groupSendToken != null) { + unauthLabel = ' (unauth+gse)'; + } else if (accessKey === ZERO_ACCESS_KEY) { + unauthLabel = ' (unauth+zero-key)'; + } else if (accessKey != null) { + unauthLabel = ' (unauth+key)'; + } else { + unauthLabel = ' (unauth)'; + } + } const logId = `${options.type} ${logType} ${redactedURL}${unauthLabel}`; log.info(logId); @@ -375,7 +389,6 @@ async function _promiseAjax( fetchOptions.headers['Content-Length'] = contentLength.toString(); } - const { accessKey, basicAuth, groupSendToken, unauthenticated } = options; if (basicAuth) { fetchOptions.headers.Authorization = `Basic ${basicAuth}`; } else if (unauthenticated) { diff --git a/ts/types/SealedSender.ts b/ts/types/SealedSender.ts index a270ee7424..55ed8f3d8b 100644 --- a/ts/types/SealedSender.ts +++ b/ts/types/SealedSender.ts @@ -1,6 +1,8 @@ // Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only +export const ZERO_ACCESS_KEY = 'AAAAAAAAAAAAAAAAAAAAAA=='; + export enum SEALED_SENDER { UNKNOWN = 0, ENABLED = 1, diff --git a/ts/util/getSendOptions.ts b/ts/util/getSendOptions.ts index e9854a2f82..aa383db9fa 100644 --- a/ts/util/getSendOptions.ts +++ b/ts/util/getSendOptions.ts @@ -7,23 +7,16 @@ import type { SendMetadataType, SendOptionsType, } from '../textsecure/SendMessage'; -import * as Bytes from '../Bytes'; -import { getRandomBytes, getZeroes } from '../Crypto'; import { getConversationMembers } from './getConversationMembers'; import { isDirectConversation, isMe } from './whatTypeOfConversation'; import { senderCertificateService } from '../services/senderCertificate'; import { shouldSharePhoneNumberWith } from './phoneNumberSharingMode'; import type { SerializedCertificateType } from '../textsecure/OutgoingMessage'; import { SenderCertificateMode } from '../textsecure/OutgoingMessage'; +import { ZERO_ACCESS_KEY, SEALED_SENDER } from '../types/SealedSender'; import { isNotNil } from './isNotNil'; import { maybeCreateGroupSendEndorsementState } from './groupSendEndorsements'; - -const SEALED_SENDER = { - UNKNOWN: 0, - ENABLED: 1, - DISABLED: 2, - UNRESTRICTED: 3, -}; +import { missingCaseError } from './missingCaseError'; export async function getSendOptionsForRecipients( recipients: ReadonlyArray, @@ -94,61 +87,69 @@ export async function getSendOptions( }; } - const { accessKey, sealedSender } = conversationAttrs; + const { accessKey } = conversationAttrs; const { e164, serviceId } = conversationAttrs; + let sealedSender = conversationAttrs.sealedSender as + | SEALED_SENDER + | undefined; + const senderCertificate = await getSenderCertificateForDirectConversation(conversationAttrs); let identifierData: SendIdentifierData | null = null; - // If we've never fetched user's profile, we default to what we have - if (sealedSender === SEALED_SENDER.UNKNOWN || story) { - identifierData = { - accessKey: - accessKey || - (story - ? Bytes.toBase64(getZeroes(16)) - : Bytes.toBase64(getRandomBytes(16))), - senderCertificate, - groupSendToken: null, - }; + if (story) { + // Always send story using zero access key + sealedSender = SEALED_SENDER.UNRESTRICTED; } - if (sealedSender === SEALED_SENDER.DISABLED) { - if (serviceId != null && groupId != null) { - const { state: groupSendEndorsementState, didRefreshGroupState } = - await maybeCreateGroupSendEndorsementState( - groupId, - alreadyRefreshedGroupState - ); + switch (sealedSender) { + case SEALED_SENDER.DISABLED: + // Try to get GSE token + if (serviceId != null && groupId != null) { + const { state: groupSendEndorsementState, didRefreshGroupState } = + await maybeCreateGroupSendEndorsementState( + groupId, + alreadyRefreshedGroupState + ); - if ( - groupSendEndorsementState != null && - groupSendEndorsementState.hasMember(serviceId) - ) { - const token = groupSendEndorsementState.buildToken( - new Set([serviceId]) - ); - if (token != null) { - identifierData = { - accessKey: null, - senderCertificate, - groupSendToken: token, - }; + if ( + groupSendEndorsementState != null && + groupSendEndorsementState.hasMember(serviceId) + ) { + const token = groupSendEndorsementState.buildToken( + new Set([serviceId]) + ); + if (token != null) { + identifierData = { + accessKey: null, + senderCertificate, + groupSendToken: token, + }; + } + } else if (didRefreshGroupState && !alreadyRefreshedGroupState) { + return getSendOptions(conversationAttrs, options, true); } - } else if (didRefreshGroupState && !alreadyRefreshedGroupState) { - return getSendOptions(conversationAttrs, options, true); } - } - } else { - identifierData = { - accessKey: - accessKey && sealedSender === SEALED_SENDER.ENABLED - ? accessKey - : Bytes.toBase64(getRandomBytes(16)), - senderCertificate, - groupSendToken: null, - }; + break; + case SEALED_SENDER.UNRESTRICTED: + identifierData = { + accessKey: ZERO_ACCESS_KEY, + senderCertificate, + groupSendToken: null, + }; + break; + case SEALED_SENDER.ENABLED: + case SEALED_SENDER.UNKNOWN: + case undefined: + identifierData = { + accessKey: accessKey || ZERO_ACCESS_KEY, + senderCertificate, + groupSendToken: null, + }; + break; + default: + throw missingCaseError(sealedSender); } let sendMetadata: SendMetadataType = {}; diff --git a/ts/util/sendToGroup.ts b/ts/util/sendToGroup.ts index 8ac4f5a56c..93ed77c533 100644 --- a/ts/util/sendToGroup.ts +++ b/ts/util/sendToGroup.ts @@ -13,7 +13,6 @@ import { SenderCertificate, UnidentifiedSenderMessageContent, } from '@signalapp/libsignal-client'; -import * as Bytes from '../Bytes'; import { senderCertificateService } from '../services/senderCertificate'; import type { SendLogCallbackType } from '../textsecure/OutgoingMessage'; import { @@ -53,7 +52,7 @@ import type { } from '../model-types.d'; import type { SendTypesType } from './handleMessageSend'; import { handleMessageSend, shouldSaveProto } from './handleMessageSend'; -import { SEALED_SENDER } from '../types/SealedSender'; +import { SEALED_SENDER, ZERO_ACCESS_KEY } from '../types/SealedSender'; import { parseIntOrThrow } from './parseIntOrThrow'; import { multiRecipient200ResponseSchema, @@ -87,7 +86,6 @@ const DAY = 24 * HOUR; const MAX_RECURSION = 10; const ACCESS_KEY_LENGTH = 16; -const ZERO_ACCESS_KEY = Bytes.toBase64(new Uint8Array(ACCESS_KEY_LENGTH)); // Public API: