Backups: use new locatorInfo & mediaName (#10627)

This commit is contained in:
trevor-signal
2025-06-18 13:16:29 -04:00
committed by GitHub
parent 099f94a809
commit fb0c414702
78 changed files with 2271 additions and 2842 deletions
+5 -2
View File
@@ -29,6 +29,7 @@ import {
signalDecrypt,
signalDecryptPreKey,
SignalMessage,
UsePQRatchet,
} from '@signalapp/libsignal-client';
import {
@@ -1777,7 +1778,8 @@ export default class MessageReceiver
identityKeyStore,
preKeyStore,
signedPreKeyStore,
kyberPreKeyStore
kyberPreKeyStore,
UsePQRatchet.No
);
}
return signalDecrypt(
@@ -1904,7 +1906,8 @@ export default class MessageReceiver
identityKeyStore,
preKeyStore,
signedPreKeyStore,
kyberPreKeyStore
kyberPreKeyStore,
UsePQRatchet.No
)
),
zone
+1 -1
View File
@@ -431,7 +431,7 @@ export class Provisioner {
.toAppUrl({
uuid,
pubKey: Bytes.toBase64(cipher.getPublicKey().serialize()),
capabilities: isLinkAndSyncEnabled() ? ['backup3'] : [],
capabilities: isLinkAndSyncEnabled() ? ['backup3', 'backup4'] : [],
})
.toString();
+1 -2
View File
@@ -6,7 +6,7 @@ import type * as client from '@signalapp/libsignal-client';
import type { SignalService as Proto } from '../protobuf';
import type { IncomingWebSocketRequest } from './WebsocketResources';
import type { ServiceIdString, AciString, PniString } from '../types/ServiceId';
import type { AttachmentType, TextAttachmentType } from '../types/Attachment';
import type { TextAttachmentType } from '../types/Attachment';
import type { GiftBadgeStates } from '../components/conversation/Message';
import type { MIMEType } from '../types/MIME';
import type { DurationInSeconds } from '../util/durations';
@@ -117,7 +117,6 @@ export type ProcessedAttachment = {
blurHash?: string;
cdnNumber?: number;
textAttachment?: Omit<TextAttachmentType, 'preview'>;
backupLocator?: AttachmentType['backupLocator'];
uploadTimestamp?: number;
downloadPath?: string;
incrementalMac?: string;
+44 -19
View File
@@ -14,10 +14,11 @@ import * as Errors from '../types/errors';
import { strictAssert } from '../util/assert';
import {
AttachmentSizeError,
mightBeOnBackupTier,
type AttachmentType,
AttachmentVariant,
AttachmentPermanentlyUndownloadableError,
hasRequiredInformationForBackup,
type BackupableAttachmentType,
} from '../types/Attachment';
import * as Bytes from '../Bytes';
import {
@@ -27,6 +28,7 @@ import {
type ReencryptedAttachmentV2,
decryptAndReencryptLocally,
measureSize,
type IntegrityCheckType,
} from '../AttachmentCrypto';
import type { ProcessedAttachment } from './Types.d';
import type { WebAPIType } from './WebAPI';
@@ -58,7 +60,7 @@ export function getCdnKey(attachment: ProcessedAttachment): string {
}
export function getBackupMediaOuterEncryptionKeyMaterial(
attachment: AttachmentType
attachment: BackupableAttachmentType
): BackupMediaKeyMaterialType {
const mediaId = getMediaIdForAttachment(attachment);
const backupKey = getBackupMediaRootKey();
@@ -66,14 +68,14 @@ export function getBackupMediaOuterEncryptionKeyMaterial(
}
function getBackupThumbnailInnerEncryptionKeyMaterial(
attachment: AttachmentType
attachment: BackupableAttachmentType
): BackupMediaKeyMaterialType {
const mediaId = getMediaIdForAttachmentThumbnail(attachment);
const backupKey = getBackupMediaRootKey();
return deriveBackupMediaKeyMaterial(backupKey, mediaId.bytes);
}
function getBackupThumbnailOuterEncryptionKeyMaterial(
attachment: AttachmentType
attachment: BackupableAttachmentType
): BackupMediaKeyMaterialType {
const mediaId = getMediaIdForAttachmentThumbnail(attachment);
const backupKey = getBackupMediaRootKey();
@@ -81,13 +83,9 @@ function getBackupThumbnailOuterEncryptionKeyMaterial(
}
export async function getCdnNumberForBackupTier(
attachment: ProcessedAttachment
attachment: BackupableAttachmentType
): Promise<number> {
strictAssert(
attachment.backupLocator,
'Attachment was missing backupLocator'
);
let backupCdnNumber = attachment.backupLocator.cdnNumber;
let { backupCdnNumber } = attachment;
if (backupCdnNumber == null) {
const mediaId = getMediaIdForAttachment(attachment);
@@ -106,11 +104,15 @@ export async function getCdnNumberForBackupTier(
export async function downloadAttachment(
server: WebAPIType,
attachment: ProcessedAttachment,
{
attachment,
mediaTier,
}:
| { attachment: AttachmentType; mediaTier: MediaTier.STANDARD }
| { attachment: BackupableAttachmentType; mediaTier: MediaTier.BACKUP },
options: {
disableRetries?: boolean;
logPrefix?: string;
mediaTier?: MediaTier;
onSizeUpdate: (totalBytes: number) => void;
timeout?: number;
variant: AttachmentVariant;
@@ -119,20 +121,20 @@ export async function downloadAttachment(
): Promise<ReencryptedAttachmentV2> {
const logId = `downloadAttachment/${options.logPrefix ?? ''}`;
const { digest, incrementalMac, chunkSize, key, size } = attachment;
const { digest, plaintextHash, incrementalMac, chunkSize, key, size } =
attachment;
try {
strictAssert(digest, `${logId}: missing digest`);
strictAssert(
digest || plaintextHash,
`${logId}: missing digest and plaintextHash`
);
strictAssert(key, `${logId}: missing key`);
strictAssert(isNumber(size), `${logId}: missing size`);
} catch (error) {
throw new AttachmentPermanentlyUndownloadableError(error.message);
}
const mediaTier =
options?.mediaTier ??
(mightBeOnBackupTier(attachment) ? MediaTier.BACKUP : MediaTier.STANDARD);
let downloadResult: Awaited<ReturnType<typeof downloadToDisk>>;
let { downloadPath } = attachment;
@@ -169,6 +171,12 @@ export async function downloadAttachment(
if (downloadOffset !== 0) {
log.info(`${logId}: resuming from ${downloadOffset}`);
}
if (mediaTier === MediaTier.BACKUP) {
strictAssert(
hasRequiredInformationForBackup(attachment),
`${logId}: attachment missing critical information for backup tier`
);
}
if (mediaTier === MediaTier.STANDARD) {
strictAssert(
@@ -197,6 +205,8 @@ export async function downloadAttachment(
size,
});
} else {
strictAssert(mediaTier === MediaTier.BACKUP, 'backup media tier');
const mediaId =
options.variant === AttachmentVariant.ThumbnailFromBackup
? getMediaIdForAttachmentThumbnail(attachment)
@@ -244,6 +254,21 @@ export async function downloadAttachment(
case AttachmentVariant.Default:
case undefined: {
const { aesKey, macKey } = splitKeys(Bytes.fromBase64(key));
let integrityCheck: IntegrityCheckType;
if (plaintextHash) {
integrityCheck = {
type: 'plaintext',
plaintextHash: Bytes.fromHex(plaintextHash),
};
} else if (digest) {
integrityCheck = {
type: 'encrypted',
digest: Bytes.fromBase64(digest),
};
} else {
throw new Error(`${logId}: missing both digest and plaintextHash`);
}
return await decryptAndReencryptLocally({
type: 'standard',
ciphertextPath: cipherTextAbsolutePath,
@@ -251,7 +276,7 @@ export async function downloadAttachment(
aesKey,
macKey,
size,
theirDigest: Bytes.fromBase64(digest),
integrityCheck,
theirIncrementalMac: incrementalMac
? Bytes.fromBase64(incrementalMac)
: undefined,
+3 -1
View File
@@ -9,6 +9,7 @@ import {
processPreKeyBundle,
ProtocolAddress,
PublicKey,
UsePQRatchet,
} from '@signalapp/libsignal-client';
import {
@@ -193,7 +194,8 @@ async function handleServerKeys(
preKeyBundle,
protocolAddress,
sessionStore,
identityKeyStore
identityKeyStore,
UsePQRatchet.No
)
);
} catch (error) {