From d036803df96c846daa99edacd04d760af14f3b06 Mon Sep 17 00:00:00 2001 From: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com> Date: Thu, 18 Aug 2022 17:31:12 -0700 Subject: [PATCH] Mirror CDS requests --- app/main.ts | 20 ++-- config/default.json | 12 +-- config/production.json | 4 +- protos/ContactDiscovery.proto | 6 +- ts/RemoteConfig.ts | 3 + ts/test-mock/bootstrap.ts | 6 +- ts/textsecure/WebAPI.ts | 146 ++++++++++++++++++----------- ts/textsecure/cds/CDSSocketBase.ts | 2 + ts/textsecure/cds/LegacyCDS.ts | 4 +- ts/textsecure/cds/Types.d.ts | 1 + ts/types/RendererConfig.ts | 52 ++++++---- ts/util/getUuidsForE164s.ts | 14 ++- 12 files changed, 173 insertions(+), 97 deletions(-) diff --git a/app/main.ts b/app/main.ts index a56b71bae..fe804025d 100644 --- a/app/main.ts +++ b/app/main.ts @@ -375,20 +375,22 @@ async function prepareUrl( const theme = await getResolvedThemeSetting(); const directoryConfig = directoryConfigSchema.safeParse({ - directoryVersion: config.get('directoryVersion') || 1, + directoryType: config.get('directoryType') || 'legacy', directoryUrl: config.get('directoryUrl') || undefined, directoryEnclaveId: config.get('directoryEnclaveId') || undefined, directoryTrustAnchor: config.get('directoryTrustAnchor') || undefined, - directoryV2Url: config.get('directoryV2Url') || undefined, - directoryV2PublicKey: - config.get('directoryV2PublicKey') || undefined, - directoryV2CodeHashes: - config.get | null>('directoryV2CodeHashes') || undefined, - directoryV3Url: config.get('directoryV3Url') || undefined, - directoryV3MRENCLAVE: - config.get('directoryV3MRENCLAVE') || undefined, + directoryCDSIUrl: + config.get('directoryCDSIUrl') || undefined, + directoryCDSIMRENCLAVE: + config.get('directoryCDSIMRENCLAVE') || undefined, + directoryCDSHUrl: + config.get('directoryCDSHUrl') || undefined, + directoryCDSHPublicKey: + config.get('directoryCDSHPublicKey') || undefined, + directoryCDSHCodeHashes: + config.get | null>('directoryCDSHCodeHashes') || undefined, }); if (!directoryConfig.success) { throw new Error( diff --git a/config/default.json b/config/default.json index e9b18e991..86aed6eb9 100644 --- a/config/default.json +++ b/config/default.json @@ -1,15 +1,15 @@ { "serverUrl": "https://chat.staging.signal.org", "storageUrl": "https://storage-staging.signal.org", - "directoryVersion": 3, + "directoryType": "cdsi", "directoryUrl": null, "directoryEnclaveId": null, "directoryTrustAnchor": null, - "directoryV2Url": null, - "directoryV2PublicKey": null, - "directoryV2CodeHashes": null, - "directoryV3Url": "https://cdsi.staging.signal.org", - "directoryV3MRENCLAVE": "ddc7b9b1cbcc932e24b9905e26c4ecbea3f9b7effd033f9e96488c2e8449f64e", + "directoryCDSIUrl": "https://cdsi.staging.signal.org", + "directoryCDSIMRENCLAVE": "ef4787a56a154ac6d009138cac17155acd23cfe4329281252365dd7c252e7fbf", + "directoryCDSHUrl": null, + "directoryCDSHPublicKey": null, + "directoryCDSHCodeHashes": null, "cdn": { "0": "https://cdn-staging.signal.org", "2": "https://cdn2-staging.signal.org" diff --git a/config/production.json b/config/production.json index 9d05e7f06..83926bcb2 100644 --- a/config/production.json +++ b/config/production.json @@ -1,10 +1,12 @@ { "serverUrl": "https://chat.signal.org", "storageUrl": "https://storage.signal.org", - "directoryVersion": 1, + "directoryType": "mirrored-cdsi", "directoryUrl": "https://api.directory.signal.org", "directoryEnclaveId": "c98e00a4e3ff977a56afefe7362a27e4961e4f19e211febfbb19b897e6b80b15", "directoryTrustAnchor": "-----BEGIN CERTIFICATE-----\nMIIFSzCCA7OgAwIBAgIJANEHdl0yo7CUMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNV\nBAYTAlVTMQswCQYDVQQIDAJDQTEUMBIGA1UEBwwLU2FudGEgQ2xhcmExGjAYBgNV\nBAoMEUludGVsIENvcnBvcmF0aW9uMTAwLgYDVQQDDCdJbnRlbCBTR1ggQXR0ZXN0\nYXRpb24gUmVwb3J0IFNpZ25pbmcgQ0EwIBcNMTYxMTE0MTUzNzMxWhgPMjA0OTEy\nMzEyMzU5NTlaMH4xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEUMBIGA1UEBwwL\nU2FudGEgQ2xhcmExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0aW9uMTAwLgYDVQQD\nDCdJbnRlbCBTR1ggQXR0ZXN0YXRpb24gUmVwb3J0IFNpZ25pbmcgQ0EwggGiMA0G\nCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCfPGR+tXc8u1EtJzLA10Feu1Wg+p7e\nLmSRmeaCHbkQ1TF3Nwl3RmpqXkeGzNLd69QUnWovYyVSndEMyYc3sHecGgfinEeh\nrgBJSEdsSJ9FpaFdesjsxqzGRa20PYdnnfWcCTvFoulpbFR4VBuXnnVLVzkUvlXT\nL/TAnd8nIZk0zZkFJ7P5LtePvykkar7LcSQO85wtcQe0R1Raf/sQ6wYKaKmFgCGe\nNpEJUmg4ktal4qgIAxk+QHUxQE42sxViN5mqglB0QJdUot/o9a/V/mMeH8KvOAiQ\nbyinkNndn+Bgk5sSV5DFgF0DffVqmVMblt5p3jPtImzBIH0QQrXJq39AT8cRwP5H\nafuVeLHcDsRp6hol4P+ZFIhu8mmbI1u0hH3W/0C2BuYXB5PC+5izFFh/nP0lc2Lf\n6rELO9LZdnOhpL1ExFOq9H/B8tPQ84T3Sgb4nAifDabNt/zu6MmCGo5U8lwEFtGM\nRoOaX4AS+909x00lYnmtwsDVWv9vBiJCXRsCAwEAAaOByTCBxjBgBgNVHR8EWTBX\nMFWgU6BRhk9odHRwOi8vdHJ1c3RlZHNlcnZpY2VzLmludGVsLmNvbS9jb250ZW50\nL0NSTC9TR1gvQXR0ZXN0YXRpb25SZXBvcnRTaWduaW5nQ0EuY3JsMB0GA1UdDgQW\nBBR4Q3t2pn680K9+QjfrNXw7hwFRPDAfBgNVHSMEGDAWgBR4Q3t2pn680K9+Qjfr\nNXw7hwFRPDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkq\nhkiG9w0BAQsFAAOCAYEAeF8tYMXICvQqeXYQITkV2oLJsp6J4JAqJabHWxYJHGir\nIEqucRiJSSx+HjIJEUVaj8E0QjEud6Y5lNmXlcjqRXaCPOqK0eGRz6hi+ripMtPZ\nsFNaBwLQVV905SDjAzDzNIDnrcnXyB4gcDFCvwDFKKgLRjOB/WAqgscDUoGq5ZVi\nzLUzTqiQPmULAQaB9c6Oti6snEFJiCQ67JLyW/E83/frzCmO5Ru6WjU4tmsmy8Ra\nUd4APK0wZTGtfPXU7w+IBdG5Ez0kE1qzxGQaL4gINJ1zMyleDnbuS8UicjJijvqA\n152Sq049ESDz+1rRGc2NVEqh1KaGXmtXvqxXcTB+Ljy5Bw2ke0v8iGngFBPqCTVB\n3op5KBG3RjbF6RRSzwzuWfL7QErNC8WEy5yDVARzTA5+xmBc388v9Dm21HGfcC8O\nDD+gT9sSpssq0ascmvH49MOgjt1yoysLtdCtJW/9FZpoOypaHx0R+mJTLwPXVMrv\nDaVzWh5aiEx+idkSGMnX\n-----END CERTIFICATE-----\n", + "directoryCDSIUrl": "https://cdsi.signal.org", + "directoryCDSIMRENCLAVE": "ef4787a56a154ac6d009138cac17155acd23cfe4329281252365dd7c252e7fbf", "cdn": { "0": "https://cdn.signal.org", "2": "https://cdn2.signal.org" diff --git a/protos/ContactDiscovery.proto b/protos/ContactDiscovery.proto index 5206bbe4d..3614f8e55 100644 --- a/protos/ContactDiscovery.proto +++ b/protos/ContactDiscovery.proto @@ -1,4 +1,4 @@ -// Copyright 2021-2021 Signal Messenger, LLC +// Copyright 2021-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only package signalservice; @@ -25,6 +25,10 @@ message CDSClientRequest { // After receiving a new token from the server, send back a message just // containing a token_ack. optional bool token_ack = 7; + + // Request that, if the server allows, both ACI and PNI be returned even + // if the aci_uak_pairs don't match. + optional bool return_acis_without_uaks = 8; } message CDSClientResponse { diff --git a/ts/RemoteConfig.ts b/ts/RemoteConfig.ts index e91155010..6a5bec9c4 100644 --- a/ts/RemoteConfig.ts +++ b/ts/RemoteConfig.ts @@ -9,6 +9,9 @@ import * as log from './logging/log'; export type ConfigKeyType = | 'desktop.announcementGroup' | 'desktop.calling.audioLevelForSpeaking' + | 'desktop.cdsi' + | 'desktop.cdsi.returnAcisWithoutUaks' + | 'desktop.cdsi.mirroring' | 'desktop.clientExpiration' | 'desktop.groupCallOutboundRing' | 'desktop.internalUser' diff --git a/ts/test-mock/bootstrap.ts b/ts/test-mock/bootstrap.ts index 344270f3f..9d339deef 100644 --- a/ts/test-mock/bootstrap.ts +++ b/ts/test-mock/bootstrap.ts @@ -326,9 +326,9 @@ export class Bootstrap { }, updatesEnabled: false, - directoryVersion: 3, - directoryV3Url: url, - directoryV3MRENCLAVE: + directoreType: 'cdsi', + directoryCDSIUrl: url, + directoryCDSIMRENCLAVE: '51133fecb3fa18aaf0c8f64cb763656d3272d9faaacdb26ae7df082e414fb142', ...this.options.extraConfig, diff --git a/ts/textsecure/WebAPI.ts b/ts/textsecure/WebAPI.ts index dfc2e5ccf..908adc48b 100644 --- a/ts/textsecure/WebAPI.ts +++ b/ts/textsecure/WebAPI.ts @@ -33,13 +33,18 @@ import { toLogFormat } from '../types/errors'; import { isPackIdValid, redactPackId } from '../types/Stickers'; import type { UUID, UUIDStringType } from '../types/UUID'; import { UUIDKind } from '../types/UUID'; +import type { DirectoryConfigType } from '../types/RendererConfig'; import * as Bytes from '../Bytes'; import { getRandomValue } from '../Crypto'; import * as linkPreviewFetch from '../linkPreviews/linkPreviewFetch'; import { isBadgeImageFileUrlValid } from '../badges/isBadgeImageFileUrlValid'; import { SocketManager } from './SocketManager'; -import type { CDSAuthType, CDSResponseType } from './cds/Types.d'; +import type { + CDSAuthType, + CDSRequestOptionsType, + CDSResponseType, +} from './cds/Types.d'; import type { CDSBase } from './cds/CDSBase'; import { LegacyCDS } from './cds/LegacyCDS'; import type { LegacyCDSPutAttestationResponseType } from './cds/LegacyCDS'; @@ -548,40 +553,6 @@ const WEBSOCKET_CALLS = new Set([ 'storageToken', ]); -type DirectoryV1OptionsType = Readonly<{ - directoryVersion: 1; - directoryUrl: string; - directoryEnclaveId: string; - directoryTrustAnchor: string; -}>; - -type DirectoryV2OptionsType = Readonly<{ - directoryVersion: 2; - directoryV2Url: string; - directoryV2PublicKey: string; - directoryV2CodeHashes: ReadonlyArray; -}>; - -type DirectoryV3OptionsType = Readonly<{ - directoryVersion: 3; - directoryV3Url: string; - directoryV3MRENCLAVE: string; -}>; - -type OptionalDirectoryFieldsType = { - directoryUrl?: unknown; - directoryEnclaveId?: unknown; - directoryTrustAnchor?: unknown; - directoryV2Url?: unknown; - directoryV2PublicKey?: unknown; - directoryV2CodeHashes?: unknown; - directoryV3Url?: unknown; - directoryV3MRENCLAVE?: unknown; -}; - -type DirectoryOptionsType = OptionalDirectoryFieldsType & - (DirectoryV1OptionsType | DirectoryV2OptionsType | DirectoryV3OptionsType); - type InitializeOptionsType = { url: string; storageUrl: string; @@ -594,7 +565,7 @@ type InitializeOptionsType = { contentProxyUrl: string; proxyUrl: string | undefined; version: string; - directoryConfig: DirectoryOptionsType; + directoryConfig: DirectoryConfigType; }; export type MessageType = Readonly<{ @@ -770,6 +741,9 @@ export type CdsLookupOptionsType = Readonly<{ e164s: ReadonlyArray; acis?: ReadonlyArray; accessKeys?: ReadonlyArray; + returnAcisWithoutUaks?: boolean; + isLegacy: boolean; + isMirroring: boolean; }>; type GetProfileCommonOptionsType = Readonly< @@ -1105,12 +1079,17 @@ export function initialize({ socketManager.authenticate({ username, password }); } - let cds: CDSBase; - if (directoryConfig.directoryVersion === 1) { - const { directoryUrl, directoryEnclaveId, directoryTrustAnchor } = - directoryConfig; + const { + directoryType, + directoryUrl, + directoryEnclaveId, + directoryTrustAnchor, + } = directoryConfig; - cds = new LegacyCDS({ + let legacyCDS: LegacyCDS | undefined; + let cds: CDSBase; + if (directoryType === 'legacy' || directoryType === 'mirrored-cdsi') { + legacyCDS = new LegacyCDS({ logger: log, directoryEnclaveId, directoryTrustAnchor, @@ -1183,17 +1162,20 @@ export function initialize({ })) as CDSAuthType; }, }); - } else if (directoryConfig.directoryVersion === 2) { - const { directoryV2Url, directoryV2PublicKey, directoryV2CodeHashes } = - directoryConfig; - cds = new CDSH({ + if (directoryType === 'legacy') { + cds = legacyCDS; + } + } + if (directoryType === 'cdsi' || directoryType === 'mirrored-cdsi') { + const { directoryCDSIUrl, directoryCDSIMRENCLAVE } = directoryConfig; + + cds = new CDSI({ logger: log, proxyUrl, - url: directoryV2Url, - publicKey: directoryV2PublicKey, - codeHashes: directoryV2CodeHashes, + url: directoryCDSIUrl, + mrenclave: directoryCDSIMRENCLAVE, certificateAuthority, version, @@ -1205,15 +1187,21 @@ export function initialize({ })) as CDSAuthType; }, }); - } else if (directoryConfig.directoryVersion === 3) { - const { directoryV3Url, directoryV3MRENCLAVE } = directoryConfig; + } + if (directoryType === 'cdsh') { + const { + directoryCDSHUrl, + directoryCDSHPublicKey, + directoryCDSHCodeHashes, + } = directoryConfig; - cds = new CDSI({ + cds = new CDSH({ logger: log, proxyUrl, - url: directoryV3Url, - mrenclave: directoryV3MRENCLAVE, + url: directoryCDSHUrl, + publicKey: directoryCDSHPublicKey, + codeHashes: directoryCDSHCodeHashes, certificateAuthority, version, @@ -2851,16 +2839,64 @@ export function initialize({ return socketManager.getProvisioningResource(handler); } + async function mirroredCdsLookup( + requestOptions: CDSRequestOptionsType, + expectedMapPromise: Promise + ): Promise { + try { + log.info('cdsLookup: sending mirrored request'); + const actualMap = await cds.request(requestOptions); + + const expectedMap = await expectedMapPromise; + let matched = 0; + for (const [e164, { aci }] of actualMap) { + if (!aci) { + continue; + } + + const expectedACI = expectedMap.get(e164)?.aci; + if (expectedACI === aci) { + matched += 1; + } else { + log.warn( + `cdsLookup: mirrored request has aci=${aci} for ${e164}, while ` + + `expected aci=${expectedACI}` + ); + } + } + + log.info(`cdsLookup: mirrored request success, matched=${matched}`); + } catch (error) { + log.error('cdsLookup: mirrored request error', toLogFormat(error)); + } + } + async function cdsLookup({ e164s, acis = [], accessKeys = [], + returnAcisWithoutUaks, + isLegacy, + isMirroring, }: CdsLookupOptionsType): Promise { - return cds.request({ + const requestOptions = { e164s, acis, accessKeys, - }); + returnAcisWithoutUaks, + }; + if (!isLegacy || !legacyCDS) { + return cds.request(requestOptions); + } + + const legacyRequest = legacyCDS.request(requestOptions); + + if (legacyCDS !== cds && isMirroring) { + // Intentionally not awaiting + mirroredCdsLookup(requestOptions, legacyRequest); + } + + return legacyRequest; } } } diff --git a/ts/textsecure/cds/CDSSocketBase.ts b/ts/textsecure/cds/CDSSocketBase.ts index 34a40f491..707318091 100644 --- a/ts/textsecure/cds/CDSSocketBase.ts +++ b/ts/textsecure/cds/CDSSocketBase.ts @@ -70,6 +70,7 @@ export abstract class CDSSocketBase< e164s, acis, accessKeys, + returnAcisWithoutUaks = false, }: CDSRequestOptionsType): Promise { const log = this.logger; @@ -109,6 +110,7 @@ export abstract class CDSSocketBase< }) ), aciUakPairs: Buffer.concat(aciUakPairs), + returnAcisWithoutUaks, }).finish(); log.info(`CDSSocket.request(): sending version=${version} request`); diff --git a/ts/textsecure/cds/LegacyCDS.ts b/ts/textsecure/cds/LegacyCDS.ts index 374126b60..0a87987ab 100644 --- a/ts/textsecure/cds/LegacyCDS.ts +++ b/ts/textsecure/cds/LegacyCDS.ts @@ -170,8 +170,8 @@ export class LegacyCDS extends CDSBase { for (const [i, e164] of e164s.entries()) { const uuid = uuids[i]; result.set(e164, { - aci: undefined, - pni: uuid ? UUID.cast(uuid) : undefined, + aci: uuid ? UUID.cast(uuid) : undefined, + pni: undefined, }); } diff --git a/ts/textsecure/cds/Types.d.ts b/ts/textsecure/cds/Types.d.ts index cbd3a9906..e8eae7500 100644 --- a/ts/textsecure/cds/Types.d.ts +++ b/ts/textsecure/cds/Types.d.ts @@ -19,5 +19,6 @@ export type CDSRequestOptionsType = Readonly<{ e164s: ReadonlyArray; acis: ReadonlyArray; accessKeys: ReadonlyArray; + returnAcisWithoutUaks?: boolean; timeout?: number; }>; diff --git a/ts/types/RendererConfig.ts b/ts/types/RendererConfig.ts index edffd7d7f..13119da40 100644 --- a/ts/types/RendererConfig.ts +++ b/ts/types/RendererConfig.ts @@ -18,24 +18,35 @@ export type configOptionalStringType = z.infer< typeof configOptionalStringSchema >; -const directoryV1ConfigSchema = z.object({ - directoryVersion: z.literal(1), +const directoryLegacyConfigSchema = z.object({ + directoryType: z.literal('legacy'), directoryEnclaveId: configRequiredStringSchema, directoryTrustAnchor: configRequiredStringSchema, directoryUrl: configRequiredStringSchema, }); -const directoryV2ConfigSchema = z.object({ - directoryVersion: z.literal(2), - directoryV2CodeHashes: z.array(z.string().nonempty()), - directoryV2PublicKey: configRequiredStringSchema, - directoryV2Url: configRequiredStringSchema, +const directoryCDSIConfigSchema = z.object({ + directoryType: z.literal('cdsi'), + directoryCDSIUrl: configRequiredStringSchema, + directoryCDSIMRENCLAVE: configRequiredStringSchema, }); -const directoryV3ConfigSchema = z.object({ - directoryVersion: z.literal(3), - directoryV3Url: configRequiredStringSchema, - directoryV3MRENCLAVE: configRequiredStringSchema, +const directoryMirroredCDSIConfigSchema = z.object({ + directoryType: z.literal('mirrored-cdsi'), + + directoryEnclaveId: configRequiredStringSchema, + directoryTrustAnchor: configRequiredStringSchema, + directoryUrl: configRequiredStringSchema, + + directoryCDSIUrl: configRequiredStringSchema, + directoryCDSIMRENCLAVE: configRequiredStringSchema, +}); + +const directoryCDSHConfigSchema = z.object({ + directoryType: z.literal('cdsh'), + directoryCDSHCodeHashes: z.array(z.string().nonempty()), + directoryCDSHPublicKey: configRequiredStringSchema, + directoryCDSHUrl: configRequiredStringSchema, }); export const directoryConfigSchema = z @@ -44,16 +55,19 @@ export const directoryConfigSchema = z directoryEnclaveId: configOptionalUnknownSchema, directoryTrustAnchor: configOptionalUnknownSchema, directoryUrl: configOptionalUnknownSchema, - directoryV2CodeHashes: configOptionalUnknownSchema, - directoryV2PublicKey: configOptionalUnknownSchema, - directoryV2Url: configOptionalUnknownSchema, - directoryV3Url: configOptionalUnknownSchema, - directoryV3MRENCLAVE: configOptionalUnknownSchema, + + directoryCDSIUrl: configOptionalUnknownSchema, + directoryCDSIMRENCLAVE: configOptionalUnknownSchema, + + directoryCDSHCodeHashes: configOptionalUnknownSchema, + directoryCDSHPublicKey: configOptionalUnknownSchema, + directoryCDSHUrl: configOptionalUnknownSchema, }) .and( - directoryV1ConfigSchema - .or(directoryV2ConfigSchema) - .or(directoryV3ConfigSchema) + directoryLegacyConfigSchema + .or(directoryMirroredCDSIConfigSchema) + .or(directoryCDSIConfigSchema) + .or(directoryCDSHConfigSchema) ); export type DirectoryConfigType = z.infer; diff --git a/ts/util/getUuidsForE164s.ts b/ts/util/getUuidsForE164s.ts index adcc785b5..e7b3f8d0c 100644 --- a/ts/util/getUuidsForE164s.ts +++ b/ts/util/getUuidsForE164s.ts @@ -5,6 +5,7 @@ import type { CDSResponseType } from '../textsecure/cds/Types.d'; import type { WebAPIType } from '../textsecure/WebAPI'; import type { UUIDStringType } from '../types/UUID'; import * as log from '../logging/log'; +import { isEnabled } from '../RemoteConfig'; import { isDirectConversation, isMe } from './whatTypeOfConversation'; export async function getUuidsForE164s( @@ -37,9 +38,20 @@ export async function getUuidsForE164s( accessKeys.push(accessKey); } + const returnAcisWithoutUaks = isEnabled('desktop.cdsi.returnAcisWithoutUaks'); + const isCDSI = isEnabled('desktop.cdsi'); + const isMirroring = isEnabled('desktop.cdsi.mirroring'); + log.info( `getUuidsForE164s(${e164s}): acis=${acis.length} ` + `accessKeys=${accessKeys.length}` ); - return server.cdsLookup({ e164s, acis, accessKeys }); + return server.cdsLookup({ + e164s, + acis, + accessKeys, + returnAcisWithoutUaks, + isLegacy: !isCDSI, + isMirroring, + }); }