// Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import { PreKeyBundle, processPreKeyBundle, ProtocolAddress, PublicKey, } from '@signalapp/libsignal-client'; import { UnregisteredUserError, HTTPError, OutgoingIdentityKeyError, } from './Errors'; import { Sessions, IdentityKeys } from '../LibSignalStores'; import { Address } from '../types/Address'; import { QualifiedAddress } from '../types/QualifiedAddress'; import { UUID } from '../types/UUID'; import type { ServerKeysType, WebAPIType } from './WebAPI'; import * as log from '../logging/log'; import { isRecord } from '../util/isRecord'; export async function getKeysForIdentifier( identifier: string, server: WebAPIType, devicesToUpdate?: Array, accessKey?: string ): Promise<{ accessKeyFailed?: boolean }> { try { const { keys, accessKeyFailed } = await getServerKeys( identifier, server, accessKey ); await handleServerKeys(identifier, keys, devicesToUpdate); return { accessKeyFailed, }; } catch (error) { if (error instanceof HTTPError && error.code === 404) { const theirUuid = UUID.lookup(identifier); if (theirUuid) { await window.textsecure.storage.protocol.archiveAllSessions(theirUuid); } throw new UnregisteredUserError(identifier, error); } throw error; } } async function getServerKeys( identifier: string, server: WebAPIType, accessKey?: string ): Promise<{ accessKeyFailed?: boolean; keys: ServerKeysType }> { try { if (!accessKey) { return { keys: await server.getKeysForIdentifier(identifier), }; } return { keys: await server.getKeysForIdentifierUnauth(identifier, undefined, { accessKey, }), }; } catch (error: unknown) { if ( error instanceof Error && error.message.includes('untrusted identity') ) { throw new OutgoingIdentityKeyError(identifier); } if ( accessKey && isRecord(error) && typeof error.code === 'number' && (error.code === 401 || error.code === 403) ) { return { accessKeyFailed: true, keys: await server.getKeysForIdentifier(identifier), }; } throw error; } } async function handleServerKeys( identifier: string, response: ServerKeysType, devicesToUpdate?: Array ): Promise { const ourUuid = window.textsecure.storage.user.getCheckedUuid(); const sessionStore = new Sessions({ ourUuid }); const identityKeyStore = new IdentityKeys({ ourUuid }); await Promise.all( response.devices.map(async device => { const { deviceId, registrationId, preKey, signedPreKey } = device; if ( devicesToUpdate !== undefined && !devicesToUpdate.includes(deviceId) ) { return; } if (device.registrationId === 0) { log.info( `handleServerKeys/${identifier}: Got device registrationId zero!` ); } if (!signedPreKey) { throw new Error( `getKeysForIdentifier/${identifier}: Missing signed prekey for deviceId ${deviceId}` ); } const theirUuid = UUID.checkedLookup(identifier); const protocolAddress = ProtocolAddress.new( theirUuid.toString(), deviceId ); const preKeyId = preKey?.keyId || null; const preKeyObject = preKey ? PublicKey.deserialize(Buffer.from(preKey.publicKey)) : null; const signedPreKeyObject = PublicKey.deserialize( Buffer.from(signedPreKey.publicKey) ); const identityKey = PublicKey.deserialize( Buffer.from(response.identityKey) ); const preKeyBundle = PreKeyBundle.new( registrationId, deviceId, preKeyId, preKeyObject, signedPreKey.keyId, signedPreKeyObject, Buffer.from(signedPreKey.signature), identityKey ); const address = new QualifiedAddress( ourUuid, new Address(theirUuid, deviceId) ); await window.textsecure.storage.protocol .enqueueSessionJob(address, () => processPreKeyBundle( preKeyBundle, protocolAddress, sessionStore, identityKeyStore ) ) .catch(error => { if (error?.message?.includes('untrusted identity for address')) { // eslint-disable-next-line no-param-reassign error.identityKey = response.identityKey; } throw error; }); }) ); }