Be resilient to invalid incrementalMac value
This commit is contained in:
parent
514509e2c7
commit
53b16c7484
7 changed files with 71 additions and 41 deletions
|
|
@ -192,17 +192,19 @@ export default {
|
||||||
),
|
),
|
||||||
validateBackup: async () => {
|
validateBackup: async () => {
|
||||||
return {
|
return {
|
||||||
totalBytes: 100,
|
result: {
|
||||||
stats: {
|
totalBytes: 100,
|
||||||
adHocCalls: 1,
|
stats: {
|
||||||
callLinks: 2,
|
adHocCalls: 1,
|
||||||
conversations: 3,
|
callLinks: 2,
|
||||||
chats: 4,
|
conversations: 3,
|
||||||
distributionLists: 5,
|
chats: 4,
|
||||||
messages: 6,
|
distributionLists: 5,
|
||||||
skippedMessages: 7,
|
messages: 6,
|
||||||
stickerPacks: 8,
|
skippedMessages: 7,
|
||||||
fixedDirectMessages: 9,
|
stickerPacks: 8,
|
||||||
|
fixedDirectMessages: 9,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import classNames from 'classnames';
|
||||||
import * as LocaleMatcher from '@formatjs/intl-localematcher';
|
import * as LocaleMatcher from '@formatjs/intl-localematcher';
|
||||||
|
|
||||||
import type { MediaDeviceSettings } from '../types/Calling';
|
import type { MediaDeviceSettings } from '../types/Calling';
|
||||||
import type { ExportResultType as BackupExportResultType } from '../services/backups';
|
import type { ValidationResultType as BackupValidationResultType } from '../services/backups';
|
||||||
import type {
|
import type {
|
||||||
AutoDownloadAttachmentType,
|
AutoDownloadAttachmentType,
|
||||||
NotificationSettingType,
|
NotificationSettingType,
|
||||||
|
|
@ -178,7 +178,7 @@ type PropsFunctionType = {
|
||||||
value: CustomColorType;
|
value: CustomColorType;
|
||||||
}
|
}
|
||||||
) => unknown;
|
) => unknown;
|
||||||
validateBackup: () => Promise<BackupExportResultType>;
|
validateBackup: () => Promise<BackupValidationResultType>;
|
||||||
|
|
||||||
// Change handlers
|
// Change handlers
|
||||||
onAudioNotificationsChange: CheckboxChangeHandlerType;
|
onAudioNotificationsChange: CheckboxChangeHandlerType;
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import React, { useState, useCallback } from 'react';
|
||||||
import type { LocalizerType } from '../types/I18N';
|
import type { LocalizerType } from '../types/I18N';
|
||||||
import { toLogFormat } from '../types/errors';
|
import { toLogFormat } from '../types/errors';
|
||||||
import { formatFileSize } from '../util/formatFileSize';
|
import { formatFileSize } from '../util/formatFileSize';
|
||||||
import type { ExportResultType as BackupExportResultType } from '../services/backups';
|
import type { ValidationResultType as BackupValidationResultType } from '../services/backups';
|
||||||
import { SettingsRow, SettingsControl } from './PreferencesUtil';
|
import { SettingsRow, SettingsControl } from './PreferencesUtil';
|
||||||
import { Button, ButtonVariant } from './Button';
|
import { Button, ButtonVariant } from './Button';
|
||||||
import { Spinner } from './Spinner';
|
import { Spinner } from './Spinner';
|
||||||
|
|
@ -15,26 +15,20 @@ export function PreferencesInternal({
|
||||||
validateBackup: doValidateBackup,
|
validateBackup: doValidateBackup,
|
||||||
}: {
|
}: {
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
validateBackup: () => Promise<BackupExportResultType>;
|
validateBackup: () => Promise<BackupValidationResultType>;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const [isValidationPending, setIsValidationPending] = useState(false);
|
const [isValidationPending, setIsValidationPending] = useState(false);
|
||||||
const [validationResult, setValidationResult] = useState<
|
const [validationResult, setValidationResult] = useState<
|
||||||
| {
|
BackupValidationResultType | undefined
|
||||||
result: BackupExportResultType;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
error: Error;
|
|
||||||
}
|
|
||||||
| undefined
|
|
||||||
>();
|
>();
|
||||||
|
|
||||||
const validateBackup = useCallback(async () => {
|
const validateBackup = useCallback(async () => {
|
||||||
setIsValidationPending(true);
|
setIsValidationPending(true);
|
||||||
setValidationResult(undefined);
|
setValidationResult(undefined);
|
||||||
try {
|
try {
|
||||||
setValidationResult({ result: await doValidateBackup() });
|
setValidationResult(await doValidateBackup());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setValidationResult({ error });
|
setValidationResult({ error: toLogFormat(error) });
|
||||||
} finally {
|
} finally {
|
||||||
setIsValidationPending(false);
|
setIsValidationPending(false);
|
||||||
}
|
}
|
||||||
|
|
@ -61,7 +55,7 @@ export function PreferencesInternal({
|
||||||
validationElem = (
|
validationElem = (
|
||||||
<div className="Preferences--internal--validate-backup--error">
|
<div className="Preferences--internal--validate-backup--error">
|
||||||
<pre>
|
<pre>
|
||||||
<code>{toLogFormat(error)}</code>
|
<code>{error}</code>
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ import { BackupImportStream } from './import';
|
||||||
import { getKeyMaterial } from './crypto';
|
import { getKeyMaterial } from './crypto';
|
||||||
import { BackupCredentials } from './credentials';
|
import { BackupCredentials } from './credentials';
|
||||||
import { BackupAPI } from './api';
|
import { BackupAPI } from './api';
|
||||||
import { validateBackup } from './validator';
|
import { validateBackup, ValidationType } from './validator';
|
||||||
import { BackupType } from './types';
|
import { BackupType } from './types';
|
||||||
import {
|
import {
|
||||||
BackupInstallerError,
|
BackupInstallerError,
|
||||||
|
|
@ -103,6 +103,15 @@ export type ExportResultType = Readonly<{
|
||||||
stats: Readonly<StatsType>;
|
stats: Readonly<StatsType>;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
export type ValidationResultType = Readonly<
|
||||||
|
| {
|
||||||
|
result: ExportResultType;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
error: string;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
export class BackupsService {
|
export class BackupsService {
|
||||||
#isStarted = false;
|
#isStarted = false;
|
||||||
#isRunning: 'import' | 'export' | false = false;
|
#isRunning: 'import' | 'export' | false = false;
|
||||||
|
|
@ -322,26 +331,40 @@ export class BackupsService {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (backupType === BackupType.Ciphertext) {
|
if (backupType === BackupType.Ciphertext) {
|
||||||
await validateBackup(() => new FileStream(path), totalBytes);
|
await validateBackup(
|
||||||
|
() => new FileStream(path),
|
||||||
|
totalBytes,
|
||||||
|
isTestOrMockEnvironment()
|
||||||
|
? ValidationType.Internal
|
||||||
|
: ValidationType.Export
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return totalBytes;
|
return totalBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test harness
|
// Test harness
|
||||||
public async validate(
|
public async _internalValidate(
|
||||||
backupLevel: BackupLevel = BackupLevel.Free,
|
backupLevel: BackupLevel = BackupLevel.Free,
|
||||||
backupType = BackupType.Ciphertext
|
backupType = BackupType.Ciphertext
|
||||||
): Promise<ExportResultType> {
|
): Promise<ValidationResultType> {
|
||||||
const { data, ...result } = await this.exportBackupData(
|
const { data, ...result } = await this.exportBackupData(
|
||||||
backupLevel,
|
backupLevel,
|
||||||
backupType
|
backupType
|
||||||
);
|
);
|
||||||
const buffer = Buffer.from(data);
|
const buffer = Buffer.from(data);
|
||||||
|
|
||||||
await validateBackup(() => new MemoryStream(buffer), buffer.byteLength);
|
try {
|
||||||
|
await validateBackup(
|
||||||
|
() => new MemoryStream(buffer),
|
||||||
|
buffer.byteLength,
|
||||||
|
ValidationType.Internal
|
||||||
|
);
|
||||||
|
|
||||||
return result;
|
return { result };
|
||||||
|
} catch (error) {
|
||||||
|
return { error: Errors.toLogFormat(error) };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test harness
|
// Test harness
|
||||||
|
|
|
||||||
|
|
@ -180,9 +180,11 @@ export async function getFilePointerForAttachment({
|
||||||
}> {
|
}> {
|
||||||
const filePointerRootProps = new Backups.FilePointer({
|
const filePointerRootProps = new Backups.FilePointer({
|
||||||
contentType: attachment.contentType,
|
contentType: attachment.contentType,
|
||||||
incrementalMac: attachment.incrementalMac
|
// Resilience to invalid data in the database from internal testing
|
||||||
? Bytes.fromBase64(attachment.incrementalMac)
|
incrementalMac:
|
||||||
: undefined,
|
typeof attachment.incrementalMac === 'string'
|
||||||
|
? Bytes.fromBase64(attachment.incrementalMac)
|
||||||
|
: undefined,
|
||||||
incrementalMacChunkSize: dropZero(attachment.chunkSize),
|
incrementalMacChunkSize: dropZero(attachment.chunkSize),
|
||||||
fileName: attachment.fileName,
|
fileName: attachment.fileName,
|
||||||
width: attachment.width,
|
width: attachment.width,
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,17 @@ import type { InputStream } from '@signalapp/libsignal-client/dist/io';
|
||||||
|
|
||||||
import { strictAssert } from '../../util/assert';
|
import { strictAssert } from '../../util/assert';
|
||||||
import { toAciObject } from '../../util/ServiceId';
|
import { toAciObject } from '../../util/ServiceId';
|
||||||
import { isTestOrMockEnvironment } from '../../environment';
|
import { missingCaseError } from '../../util/missingCaseError';
|
||||||
|
|
||||||
|
export enum ValidationType {
|
||||||
|
Export = 'Export',
|
||||||
|
Internal = 'Internal',
|
||||||
|
}
|
||||||
|
|
||||||
export async function validateBackup(
|
export async function validateBackup(
|
||||||
inputFactory: () => InputStream,
|
inputFactory: () => InputStream,
|
||||||
fileSize: number
|
fileSize: number,
|
||||||
|
type: ValidationType
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const accountEntropy = window.storage.get('accountEntropyPool');
|
const accountEntropy = window.storage.get('accountEntropyPool');
|
||||||
strictAssert(accountEntropy, 'Account Entropy Pool not available');
|
strictAssert(accountEntropy, 'Account Entropy Pool not available');
|
||||||
|
|
@ -28,12 +34,14 @@ export async function validateBackup(
|
||||||
BigInt(fileSize)
|
BigInt(fileSize)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isTestOrMockEnvironment()) {
|
if (type === ValidationType.Internal) {
|
||||||
strictAssert(
|
strictAssert(
|
||||||
outcome.ok,
|
outcome.ok,
|
||||||
`Backup validation failed: ${outcome.errorMessage}`
|
`Backup validation failed: ${outcome.errorMessage}`
|
||||||
);
|
);
|
||||||
} else {
|
} else if (type === ValidationType.Export) {
|
||||||
strictAssert(outcome.ok, 'Backup validation failed');
|
strictAssert(outcome.ok, 'Backup validation failed');
|
||||||
|
} else {
|
||||||
|
throw missingCaseError(type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import { resolveUsernameByLinkBase64 } from '../services/username';
|
||||||
import { writeProfile } from '../services/writeProfile';
|
import { writeProfile } from '../services/writeProfile';
|
||||||
import {
|
import {
|
||||||
backupsService,
|
backupsService,
|
||||||
type ExportResultType as BackupExportResultType,
|
type ValidationResultType as BackupValidationResultType,
|
||||||
} from '../services/backups';
|
} from '../services/backups';
|
||||||
import { isInCall } from '../state/selectors/calling';
|
import { isInCall } from '../state/selectors/calling';
|
||||||
import { getConversationsWithCustomColorSelector } from '../state/selectors/conversations';
|
import { getConversationsWithCustomColorSelector } from '../state/selectors/conversations';
|
||||||
|
|
@ -164,7 +164,7 @@ export type IPCEventsCallbacksType = {
|
||||||
unknownSignalLink: () => void;
|
unknownSignalLink: () => void;
|
||||||
getCustomColors: () => Record<string, CustomColorType>;
|
getCustomColors: () => Record<string, CustomColorType>;
|
||||||
syncRequest: () => Promise<void>;
|
syncRequest: () => Promise<void>;
|
||||||
validateBackup: () => Promise<BackupExportResultType>;
|
validateBackup: () => Promise<BackupValidationResultType>;
|
||||||
setGlobalDefaultConversationColor: (
|
setGlobalDefaultConversationColor: (
|
||||||
color: ConversationColorType,
|
color: ConversationColorType,
|
||||||
customColor?: { id: string; value: CustomColorType }
|
customColor?: { id: string; value: CustomColorType }
|
||||||
|
|
@ -551,7 +551,8 @@ export function createIPCEvents(
|
||||||
await sendSyncRequests();
|
await sendSyncRequests();
|
||||||
return contactSyncComplete;
|
return contactSyncComplete;
|
||||||
},
|
},
|
||||||
validateBackup: () => backupsService.validate(),
|
// Only for internal use
|
||||||
|
validateBackup: () => backupsService._internalValidate(),
|
||||||
getLastSyncTime: () => window.storage.get('synced_at'),
|
getLastSyncTime: () => window.storage.get('synced_at'),
|
||||||
setLastSyncTime: value => window.storage.put('synced_at', value),
|
setLastSyncTime: value => window.storage.put('synced_at', value),
|
||||||
getUniversalExpireTimer: () => universalExpireTimer.get(),
|
getUniversalExpireTimer: () => universalExpireTimer.get(),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue