signal-desktop/ts/textsecure/getKeysForServiceId.ts
2024-09-06 10:52:19 -07:00

206 lines
5.5 KiB
TypeScript

// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import {
ErrorCode,
KEMPublicKey,
LibSignalErrorBase,
PreKeyBundle,
processPreKeyBundle,
ProtocolAddress,
PublicKey,
} from '@signalapp/libsignal-client';
import {
OutgoingIdentityKeyError,
UnregisteredUserError,
HTTPError,
} from './Errors';
import { Sessions, IdentityKeys } from '../LibSignalStores';
import { Address } from '../types/Address';
import { QualifiedAddress } from '../types/QualifiedAddress';
import type { ServiceIdString } from '../types/ServiceId';
import type { ServerKeysType, WebAPIType } from './WebAPI';
import * as log from '../logging/log';
import { isRecord } from '../util/isRecord';
import type { GroupSendToken } from '../types/GroupSendEndorsements';
import { onFailedToSendWithEndorsements } from '../util/groupSendEndorsements';
export async function getKeysForServiceId(
serviceId: ServiceIdString,
server: WebAPIType,
devicesToUpdate: Array<number> | null,
accessKey: string | null,
groupSendToken: GroupSendToken | null
): Promise<{ accessKeyFailed?: boolean }> {
try {
const { keys, accessKeyFailed } = await getServerKeys(
serviceId,
server,
accessKey,
groupSendToken
);
await handleServerKeys(serviceId, keys, devicesToUpdate);
return {
accessKeyFailed,
};
} catch (error) {
if (error instanceof HTTPError && error.code === 404) {
await window.textsecure.storage.protocol.archiveAllSessions(serviceId);
throw new UnregisteredUserError(serviceId, error);
}
throw error;
}
}
function isUnauthorizedError(error: unknown) {
return (
isRecord(error) &&
typeof error.code === 'number' &&
(error.code === 401 || error.code === 403)
);
}
async function getServerKeys(
serviceId: ServiceIdString,
server: WebAPIType,
accessKey: string | null,
groupSendToken: GroupSendToken | null
): Promise<{ accessKeyFailed: boolean; keys: ServerKeysType }> {
// Return true only when attempted with access key
let accessKeyFailed = false;
if (accessKey != null) {
// Try the access key first
try {
const keys = await server.getKeysForServiceIdUnauth(
serviceId,
undefined,
{ accessKey }
);
return { keys, accessKeyFailed };
} catch (error) {
accessKeyFailed = true;
if (!isUnauthorizedError(error)) {
throw error;
}
}
}
if (groupSendToken != null) {
try {
const keys = await server.getKeysForServiceIdUnauth(
serviceId,
undefined,
{ groupSendToken }
);
return { keys, accessKeyFailed };
} catch (error) {
if (!isUnauthorizedError(error)) {
throw error;
} else {
onFailedToSendWithEndorsements(error);
}
}
}
return {
keys: await server.getKeysForServiceId(serviceId),
accessKeyFailed,
};
}
async function handleServerKeys(
serviceId: ServiceIdString,
response: ServerKeysType,
devicesToUpdate: Array<number> | null
): Promise<void> {
const ourAci = window.textsecure.storage.user.getCheckedAci();
const sessionStore = new Sessions({ ourServiceId: ourAci });
const identityKeyStore = new IdentityKeys({ ourServiceId: ourAci });
await Promise.all(
response.devices.map(async device => {
const { deviceId, registrationId, pqPreKey, preKey, signedPreKey } =
device;
if (devicesToUpdate != null && !devicesToUpdate.includes(deviceId)) {
return;
}
if (device.registrationId === 0) {
log.info(
`handleServerKeys/${serviceId}: Got device registrationId zero!`
);
}
if (!signedPreKey) {
throw new Error(
`getKeysForIdentifier/${serviceId}: Missing signed prekey for deviceId ${deviceId}`
);
}
const protocolAddress = ProtocolAddress.new(serviceId, 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 pqPreKeyId = pqPreKey?.keyId || null;
const pqPreKeyPublic = pqPreKey
? KEMPublicKey.deserialize(Buffer.from(pqPreKey.publicKey))
: null;
const pqPreKeySignature = pqPreKey
? Buffer.from(pqPreKey.signature)
: null;
const preKeyBundle = PreKeyBundle.new(
registrationId,
deviceId,
preKeyId,
preKeyObject,
signedPreKey.keyId,
signedPreKeyObject,
Buffer.from(signedPreKey.signature),
identityKey,
pqPreKeyId,
pqPreKeyPublic,
pqPreKeySignature
);
const address = new QualifiedAddress(
ourAci,
new Address(serviceId, deviceId)
);
try {
await window.textsecure.storage.protocol.enqueueSessionJob(
address,
`handleServerKeys(${serviceId})`,
() =>
processPreKeyBundle(
preKeyBundle,
protocolAddress,
sessionStore,
identityKeyStore
)
);
} catch (error) {
if (
error instanceof LibSignalErrorBase &&
error.code === ErrorCode.UntrustedIdentity
) {
throw new OutgoingIdentityKeyError(serviceId, error);
}
throw error;
}
})
);
}