Migrate schema to service ids

This commit is contained in:
Fedor Indutny 2023-08-16 22:54:39 +02:00 committed by Jamie Kyle
parent 71958f8a01
commit 8b0da36caa
258 changed files with 4795 additions and 2613 deletions

View file

@ -39,7 +39,12 @@ import {
generateKyberPreKey,
} from '../Curve';
import type { ServiceIdString, PniString } from '../types/ServiceId';
import { ServiceIdKind, normalizeAci, normalizePni } from '../types/ServiceId';
import {
ServiceIdKind,
normalizeAci,
toTaggedPni,
isUntaggedPniString,
} from '../types/ServiceId';
import { isMoreRecentThan, isOlderThan } from '../util/timestamp';
import { ourProfileKeyService } from '../services/ourProfileKey';
import { assertDev, strictAssert } from '../util/assert';
@ -464,7 +469,7 @@ export default class AccountManager extends EventTarget {
isConfirmed: false,
isLastResort: false,
keyId,
ourUuid: ourServiceId,
ourServiceId,
});
toUpload.push({
keyId,
@ -704,7 +709,7 @@ export default class AccountManager extends EventTarget {
isConfirmed: false,
isLastResort: true,
keyId,
ourUuid: ourServiceId,
ourServiceId,
};
await Promise.all([
@ -934,7 +939,11 @@ export default class AccountManager extends EventTarget {
});
const ourAci = normalizeAci(response.uuid, 'createAccount');
const ourPni = normalizePni(response.pni, 'createAccount');
strictAssert(
isUntaggedPniString(response.pni),
'Response pni must be untagged'
);
const ourPni = toTaggedPni(response.pni);
const uuidChanged = previousACI && ourAci && previousACI !== ourAci;
@ -1116,9 +1125,10 @@ export default class AccountManager extends EventTarget {
const logId = `AcountManager.generateKeys(${serviceIdKind})`;
const { storage } = window.textsecure;
const store = storage.protocol;
const ourUuid = storage.user.getCheckedServiceId(serviceIdKind);
const ourServiceId = storage.user.getCheckedServiceId(serviceIdKind);
const identityKey = maybeIdentityKey ?? store.getIdentityKeyPair(ourUuid);
const identityKey =
maybeIdentityKey ?? store.getIdentityKeyPair(ourServiceId);
strictAssert(identityKey, 'generateKeys: No identity key pair!');
const preKeys = await this.generateNewPreKeys(serviceIdKind, count);

View file

@ -19,6 +19,7 @@ import {
groupDecrypt,
PlaintextContent,
PreKeySignalMessage,
Pni,
processSenderKeyDistributionMessage,
ProtocolAddress,
PublicKey,
@ -48,7 +49,6 @@ import { parseIntOrThrow } from '../util/parseIntOrThrow';
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
import { Zone } from '../util/Zone';
import { DurationInSeconds, SECOND } from '../util/durations';
import { bytesToUuid } from '../util/uuidToBytes';
import type { DownloadedAttachmentType } from '../types/Attachment';
import { Address } from '../types/Address';
import { QualifiedAddress } from '../types/QualifiedAddress';
@ -62,6 +62,7 @@ import {
isAciString,
isPniString,
isServiceIdString,
fromPniObject,
} from '../types/ServiceId';
import * as Errors from '../types/errors';
@ -858,13 +859,13 @@ export default class MessageReceiver
type: decoded.type,
source: item.source,
sourceServiceId: normalizeServiceId(
item.sourceUuid || decoded.sourceServiceId,
item.sourceServiceId || decoded.sourceServiceId,
'CachedEnvelope.sourceServiceId'
),
sourceDevice: decoded.sourceDevice || item.sourceDevice,
destinationServiceId: normalizeServiceId(
decoded.destinationServiceId || item.destinationUuid || ourAci,
'CachedEnvelope.destinationUuid'
decoded.destinationServiceId || item.destinationServiceId || ourAci,
'CachedEnvelope.destinationServiceId'
),
updatedPni: decoded.updatedPni
? normalizePni(decoded.updatedPni, 'CachedEnvelope.updatedPni')
@ -1088,9 +1089,9 @@ export default class MessageReceiver
...data,
source: envelope.source,
sourceUuid: envelope.sourceServiceId,
sourceServiceId: envelope.sourceServiceId,
sourceDevice: envelope.sourceDevice,
destinationUuid: envelope.destinationServiceId,
destinationServiceId: envelope.destinationServiceId,
updatedPni: envelope.updatedPni,
serverGuid: envelope.serverGuid,
serverTimestamp: envelope.serverTimestamp,
@ -1230,7 +1231,7 @@ export default class MessageReceiver
log.warn(
'MessageReceiver.decryptAndCacheBatch: ' +
`Rejecting envelope ${getEnvelopeId(envelope)}, ` +
`unknown uuid: ${destinationServiceId}`
`unknown serviceId: ${destinationServiceId}`
);
return { plaintext: undefined, envelope: undefined };
}
@ -1585,7 +1586,7 @@ export default class MessageReceiver
!isGroupV2 &&
((envelope.source && this.isBlocked(envelope.source)) ||
(envelope.sourceServiceId &&
this.isUuidBlocked(envelope.sourceServiceId)))
this.isServiceIdBlocked(envelope.sourceServiceId)))
) {
log.info(`${logId}: Dropping non-GV2 message from blocked sender`);
this.removeFromCache(envelope);
@ -2011,7 +2012,7 @@ export default class MessageReceiver
if (
(envelope.source && this.isBlocked(envelope.source)) ||
(envelope.sourceServiceId &&
this.isUuidBlocked(envelope.sourceServiceId))
this.isServiceIdBlocked(envelope.sourceServiceId))
) {
log.info(
'MessageReceiver.decrypt: Error from blocked sender; no further processing'
@ -2024,7 +2025,14 @@ export default class MessageReceiver
if (uuid && deviceId) {
const senderAci = uuid;
strictAssert(isAciString(senderAci), 'Sender uuid must be an ACI');
if (!isAciString(senderAci)) {
log.info(
'MessageReceiver.decrypt: Error from PNI; no further processing'
);
this.removeFromCache(envelope);
throw error;
}
const { cipherTextBytes, cipherTextType } = envelope;
const event = new DecryptionErrorEvent(
{
@ -2735,7 +2743,9 @@ export default class MessageReceiver
const { pni: pniBytes, signature } = pniSignatureMessage;
strictAssert(Bytes.isNotEmpty(pniBytes), `${logId}: missing PNI bytes`);
const pni = bytesToUuid(pniBytes);
const pni = fromPniObject(
Pni.parseFromServiceIdBinary(Buffer.from(pniBytes))
);
strictAssert(pni, `${logId}: missing PNI`);
strictAssert(Bytes.isNotEmpty(signature), `${logId}: empty signature`);
strictAssert(isAciString(aci), `${logId}: invalid ACI`);
@ -2774,7 +2784,8 @@ export default class MessageReceiver
if (
(envelope.source && this.isBlocked(envelope.source)) ||
(envelope.sourceServiceId && this.isUuidBlocked(envelope.sourceServiceId))
(envelope.sourceServiceId &&
this.isServiceIdBlocked(envelope.sourceServiceId))
) {
const logId = getEnvelopeId(envelope);
@ -3572,8 +3583,8 @@ export default class MessageReceiver
return this.storage.blocked.isBlocked(number);
}
private isUuidBlocked(uuid: ServiceIdString): boolean {
return this.storage.blocked.isUuidBlocked(uuid);
private isServiceIdBlocked(serviceId: ServiceIdString): boolean {
return this.storage.blocked.isServiceIdBlocked(serviceId);
}
private isGroupBlocked(groupId: string): boolean {

View file

@ -9,6 +9,7 @@ import Long from 'long';
import PQueue from 'p-queue';
import type { PlaintextContent } from '@signalapp/libsignal-client';
import {
Pni,
ProtocolAddress,
SenderKeyDistributionMessage,
} from '@signalapp/libsignal-client';
@ -25,7 +26,7 @@ import type {
UploadedAttachmentType,
} from '../types/Attachment';
import type { AciString, ServiceIdString } from '../types/ServiceId';
import { ServiceIdKind } from '../types/ServiceId';
import { ServiceIdKind, serviceIdSchema } from '../types/ServiceId';
import type {
ChallengeType,
GetGroupLogOptionsType,
@ -67,7 +68,6 @@ import type {
import { concat, isEmpty, map } from '../util/iterables';
import type { SendTypesType } from '../util/handleMessageSend';
import { shouldSaveProto, sendTypesEnum } from '../util/handleMessageSend';
import { uuidToBytes } from '../util/uuidToBytes';
import type { DurationInSeconds } from '../util/durations';
import { SignalService as Proto } from '../protobuf';
import * as log from '../logging/log';
@ -149,7 +149,7 @@ export type ReactionType = {
export const singleProtoJobDataSchema = z.object({
contentHint: z.number(),
identifier: z.string(),
serviceId: serviceIdSchema,
isSyncMessage: z.boolean(),
messageIds: z.array(z.string()).optional(),
protoBase64: z.string(),
@ -490,7 +490,7 @@ class Message {
bodyRange.start = range.start;
bodyRange.length = range.length;
if (BodyRange.isMention(range)) {
bodyRange.mentionAci = range.mentionUuid;
bodyRange.mentionAci = range.mentionAci;
} else if (BodyRange.isFormatting(range)) {
bodyRange.style = range.style;
} else {
@ -529,7 +529,7 @@ class Message {
return {
start,
length,
mentionAci: bodyRange.mentionUuid,
mentionAci: bodyRange.mentionAci,
};
}
if (BodyRange.isFormatting(bodyRange)) {
@ -596,7 +596,9 @@ function addPniSignatureMessageToProto({
// eslint-disable-next-line no-param-reassign
proto.pniSignatureMessage = {
pni: uuidToBytes(pniSignatureMessage.pni),
pni: Pni.parseFromServiceIdString(
pniSignatureMessage.pni
).getServiceIdBinary(),
signature: pniSignatureMessage.signature,
};
}
@ -883,7 +885,7 @@ export default class MessageSender {
const blockedIdentifiers = new Set(
concat(
window.storage.blocked.getBlockedUuids(),
window.storage.blocked.getBlockedServiceIds(),
window.storage.blocked.getBlockedNumbers()
)
);
@ -1320,7 +1322,7 @@ export default class MessageSender {
return {
contentHint: ContentHint.RESENDABLE,
identifier: myAci,
serviceId: myAci,
isSyncMessage: true,
protoBase64: Bytes.toBase64(
Proto.Content.encode(contentMessage).finish()
@ -1344,7 +1346,7 @@ export default class MessageSender {
return {
contentHint: ContentHint.RESENDABLE,
identifier: myAci,
serviceId: myAci,
isSyncMessage: true,
protoBase64: Bytes.toBase64(
Proto.Content.encode(contentMessage).finish()
@ -1368,7 +1370,7 @@ export default class MessageSender {
return {
contentHint: ContentHint.RESENDABLE,
identifier: myAci,
serviceId: myAci,
isSyncMessage: true,
protoBase64: Bytes.toBase64(
Proto.Content.encode(contentMessage).finish()
@ -1393,7 +1395,7 @@ export default class MessageSender {
return {
contentHint: ContentHint.RESENDABLE,
identifier: myAci,
serviceId: myAci,
isSyncMessage: true,
protoBase64: Bytes.toBase64(
Proto.Content.encode(contentMessage).finish()
@ -1418,7 +1420,7 @@ export default class MessageSender {
return {
contentHint: ContentHint.RESENDABLE,
identifier: myAci,
serviceId: myAci,
isSyncMessage: true,
protoBase64: Bytes.toBase64(
Proto.Content.encode(contentMessage).finish()
@ -1443,7 +1445,7 @@ export default class MessageSender {
return {
contentHint: ContentHint.RESENDABLE,
identifier: myAci,
serviceId: myAci,
isSyncMessage: true,
protoBase64: Bytes.toBase64(
Proto.Content.encode(contentMessage).finish()
@ -1599,7 +1601,7 @@ export default class MessageSender {
return {
contentHint: ContentHint.RESENDABLE,
identifier: myAci,
serviceId: myAci,
isSyncMessage: true,
protoBase64: Bytes.toBase64(
Proto.Content.encode(contentMessage).finish()
@ -1640,7 +1642,7 @@ export default class MessageSender {
return {
contentHint: ContentHint.RESENDABLE,
identifier: myAci,
serviceId: myAci,
isSyncMessage: true,
protoBase64: Bytes.toBase64(
Proto.Content.encode(contentMessage).finish()
@ -1685,7 +1687,7 @@ export default class MessageSender {
return {
contentHint: ContentHint.RESENDABLE,
identifier: myAci,
serviceId: myAci,
isSyncMessage: true,
protoBase64: Bytes.toBase64(
Proto.Content.encode(contentMessage).finish()
@ -1731,7 +1733,7 @@ export default class MessageSender {
async sendDeliveryReceipt(
options: Readonly<{
senderServiceId: ServiceIdString;
senderAci: AciString;
timestamps: Array<number>;
isDirectConversation: boolean;
options?: Readonly<SendOptionsType>;
@ -1745,7 +1747,7 @@ export default class MessageSender {
async sendReadReceipt(
options: Readonly<{
senderServiceId: AciString;
senderAci: AciString;
timestamps: Array<number>;
isDirectConversation: boolean;
options?: Readonly<SendOptionsType>;
@ -1759,7 +1761,7 @@ export default class MessageSender {
async sendViewedReceipt(
options: Readonly<{
senderServiceId: ServiceIdString;
senderAci: AciString;
timestamps: Array<number>;
isDirectConversation: boolean;
options?: Readonly<SendOptionsType>;
@ -1772,13 +1774,13 @@ export default class MessageSender {
}
private async sendReceiptMessage({
senderServiceId,
senderAci,
timestamps,
type,
isDirectConversation,
options,
}: Readonly<{
senderServiceId: ServiceIdString;
senderAci: AciString;
timestamps: Array<number>;
type: Proto.ReceiptMessage.Type;
isDirectConversation: boolean;
@ -1796,7 +1798,7 @@ export default class MessageSender {
contentMessage.receiptMessage = receiptMessage;
if (isDirectConversation) {
const conversation = window.ConversationController.get(senderServiceId);
const conversation = window.ConversationController.get(senderAci);
addPniSignatureMessageToProto({
conversation,
@ -1808,7 +1810,7 @@ export default class MessageSender {
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
return this.sendIndividualProto({
serviceId: senderServiceId,
serviceId: senderAci,
proto: contentMessage,
timestamp,
contentHint: ContentHint.RESENDABLE,
@ -1873,7 +1875,7 @@ export default class MessageSender {
);
return;
}
const recipientServiceId = conversation.get('uuid');
const recipientServiceId = conversation.getServiceId();
if (!recipientServiceId) {
log.warn(
`makeSendLogCallback: Conversation ${conversation.idForLogging()} had no UUID`

View file

@ -3,7 +3,7 @@
import type { SignalService as Proto } from '../protobuf';
import type { IncomingWebSocketRequest } from './WebsocketResources';
import type { ServiceIdString, PniString } from '../types/ServiceId';
import type { ServiceIdString, AciString, PniString } from '../types/ServiceId';
import type { TextAttachmentType } from '../types/Attachment';
import type { GiftBadgeStates } from '../components/conversation/Message';
import type { MIMEType } from '../types/MIME';
@ -138,7 +138,7 @@ export type ProcessedQuoteAttachment = {
export type ProcessedQuote = {
id?: number;
authorAci?: string;
authorAci?: AciString;
text?: string;
attachments: ReadonlyArray<ProcessedQuoteAttachment>;
bodyRanges?: ReadonlyArray<ProcessedBodyRange>;
@ -173,7 +173,7 @@ export type ProcessedSticker = {
export type ProcessedReaction = {
emoji?: string;
remove: boolean;
targetAuthorAci?: string;
targetAuthorAci?: AciString;
targetTimestamp?: number;
};

View file

@ -32,12 +32,12 @@ import { createHTTPSAgent } from '../util/createHTTPSAgent';
import type { SocketStatus } from '../types/SocketStatus';
import { toLogFormat } from '../types/errors';
import { isPackIdValid, redactPackId } from '../types/Stickers';
import type { ServiceIdString, AciString, PniString } from '../types/ServiceId';
import {
ServiceIdKind,
isAciString,
isServiceIdString,
import type {
ServiceIdString,
AciString,
UntaggedPniString,
} from '../types/ServiceId';
import { ServiceIdKind, serviceIdSchema, aciSchema } from '../types/ServiceId';
import type { DirectoryConfigType } from '../types/RendererConfig';
import * as Bytes from '../Bytes';
import { randomInt } from '../Crypto';
@ -181,22 +181,6 @@ type BytesWithDetailsType = {
response: Response;
};
const aciSchema = z
.string()
.refine(isAciString)
.transform(x => {
assertDev(isAciString(x), 'Refine did not throw!');
return x;
});
const serviceIdSchema = z
.string()
.refine(isServiceIdString)
.transform(x => {
assertDev(isServiceIdString(x), 'Refine did not throw!');
return x;
});
export const multiRecipient200ResponseSchema = z.object({
uuids404: z.array(serviceIdSchema).optional(),
needsSync: z.boolean().optional(),
@ -769,7 +753,7 @@ export type WhoamiResultType = z.infer<typeof whoamiResultZod>;
export type ConfirmCodeResultType = Readonly<{
uuid: AciString;
pni: PniString;
pni: UntaggedPniString;
deviceId?: number;
}>;
@ -811,7 +795,7 @@ export type GetGroupCredentialsOptionsType = Readonly<{
}>;
export type GetGroupCredentialsResultType = Readonly<{
pni?: string | null;
pni?: UntaggedPniString | null;
credentials: ReadonlyArray<GroupCredentialType>;
}>;
@ -891,7 +875,7 @@ export type ConfirmCodeOptionsType = Readonly<{
}>;
export type ReportMessageOptionsType = Readonly<{
senderUuid: string;
senderAci: AciString;
serverGuid: string;
token?: string;
}>;
@ -2006,7 +1990,7 @@ export function initialize({
}
async function reportMessage({
senderUuid,
senderAci,
serverGuid,
token,
}: ReportMessageOptionsType): Promise<void> {
@ -2015,7 +1999,7 @@ export function initialize({
await _ajax({
call: 'reportMessage',
httpType: 'POST',
urlParameters: urlPathFromComponents([senderUuid, serverGuid]),
urlParameters: urlPathFromComponents([senderAci, serverGuid]),
responseType: 'bytes',
jsonData,
});
@ -2895,7 +2879,8 @@ export function initialize({
call: 'getGroupCredentials',
urlParameters:
`?redemptionStartSeconds=${startDayInSeconds}&` +
`redemptionEndSeconds=${endDayInSeconds}`,
`redemptionEndSeconds=${endDayInSeconds}&` +
'pniAsServiceId=true',
httpType: 'GET',
responseType: 'json',
})) as CredentialResponseType;

View file

@ -9,7 +9,11 @@ import Long from 'long';
import type { LoggerType } from '../../types/Logging';
import { strictAssert } from '../../util/assert';
import { isAciString, isUntaggedPniString } from '../../types/ServiceId';
import {
isAciString,
isUntaggedPniString,
toTaggedPni,
} from '../../types/ServiceId';
import * as Bytes from '../../Bytes';
import { UUID_BYTE_SIZE } from '../../Crypto';
import { uuidToBytes, bytesToUuid } from '../../util/uuidToBytes';
@ -256,6 +260,9 @@ function decodeSingleResponse(
'CDSI response has invalid PNI'
);
resultMap.set(e164, { pni, aci });
resultMap.set(e164, {
pni: pni === undefined ? undefined : toTaggedPni(pni),
aci,
});
}
}

View file

@ -1,7 +1,7 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { AciString, UntaggedPniString } from '../../types/ServiceId';
import type { AciString, PniString } from '../../types/ServiceId';
export type CDSAuthType = Readonly<{
username: string;
@ -10,7 +10,7 @@ export type CDSAuthType = Readonly<{
export type CDSResponseEntryType = Readonly<{
aci: AciString | undefined;
pni: UntaggedPniString | undefined;
pni: PniString | undefined;
}>;
export type CDSResponseType = ReadonlyMap<string, CDSResponseEntryType>;

View file

@ -29,6 +29,7 @@ import { SECOND, DurationInSeconds } from '../util/durations';
import type { AnyPaymentEvent } from '../types/Payment';
import { PaymentEventKind } from '../types/Payment';
import { filterAndClean } from '../types/BodyRange';
import { isAciString, normalizeAci } from '../types/ServiceId';
const FLAGS = Proto.DataMessage.Flags;
export const ATTACHMENT_MAX = 32;
@ -129,9 +130,14 @@ export function processQuote(
return undefined;
}
const { authorAci } = quote;
if (!isAciString(authorAci)) {
throw new Error('quote.authorAci is not an ACI string');
}
return {
id: quote.id?.toNumber(),
authorAci: dropNull(quote.authorAci),
authorAci: normalizeAci(authorAci, 'Quote.authorAci'),
text: dropNull(quote.text),
attachments: (quote.attachments ?? []).map(attachment => {
return {
@ -220,10 +226,15 @@ export function processReaction(
return undefined;
}
const { targetAuthorAci } = reaction;
if (!isAciString(targetAuthorAci)) {
throw new Error('reaction.targetAuthorAci is not an ACI string');
}
return {
emoji: dropNull(reaction.emoji),
remove: Boolean(reaction.remove),
targetAuthorAci: dropNull(reaction.targetAuthorAci),
targetAuthorAci: normalizeAci(targetAuthorAci, 'Reaction.targetAuthorAci'),
targetTimestamp: reaction.targetTimestamp?.toNumber(),
};
}

View file

@ -42,32 +42,34 @@ export class Blocked {
await this.storage.put(BLOCKED_NUMBERS_ID, without(numbers, number));
}
public getBlockedUuids(): Array<ServiceIdString> {
public getBlockedServiceIds(): Array<ServiceIdString> {
return this.storage.get(BLOCKED_UUIDS_ID, new Array<ServiceIdString>());
}
public isUuidBlocked(uuid: ServiceIdString): boolean {
return this.getBlockedUuids().includes(uuid);
public isServiceIdBlocked(serviceId: ServiceIdString): boolean {
return this.getBlockedServiceIds().includes(serviceId);
}
public async addBlockedUuid(uuid: ServiceIdString): Promise<void> {
const uuids = this.getBlockedUuids();
if (uuids.includes(uuid)) {
public async addBlockedServiceId(serviceId: ServiceIdString): Promise<void> {
const serviceIds = this.getBlockedServiceIds();
if (serviceIds.includes(serviceId)) {
return;
}
log.info('adding', uuid, 'to blocked list');
await this.storage.put(BLOCKED_UUIDS_ID, uuids.concat(uuid));
log.info('adding', serviceId, 'to blocked list');
await this.storage.put(BLOCKED_UUIDS_ID, serviceIds.concat(serviceId));
}
public async removeBlockedUuid(uuid: ServiceIdString): Promise<void> {
const numbers = this.getBlockedUuids();
if (!numbers.includes(uuid)) {
public async removeBlockedServiceId(
serviceId: ServiceIdString
): Promise<void> {
const numbers = this.getBlockedServiceIds();
if (!numbers.includes(serviceId)) {
return;
}
log.info('removing', uuid, 'from blocked list');
await this.storage.put(BLOCKED_UUIDS_ID, without(numbers, uuid));
log.info('removing', serviceId, 'from blocked list');
await this.storage.put(BLOCKED_UUIDS_ID, without(numbers, serviceId));
}
public getBlockedGroups(): Array<string> {

View file

@ -27,7 +27,7 @@ export type SetCredentialsOptions = {
export class User {
constructor(private readonly storage: StorageInterface) {}
public async setUuidAndDeviceId(
public async setAciAndDeviceId(
aci: AciString,
deviceId: number
): Promise<void> {