Discard invalid incrementalMacs

This commit is contained in:
trevor-signal
2025-09-08 16:19:17 -04:00
committed by GitHub
parent ebdf651dca
commit b92c0e95e8
14 changed files with 304 additions and 88 deletions

View File

@@ -219,7 +219,7 @@ describe('backups', function (this: Mocha.Suite) {
const catTimestamp = bootstrap.getTimestamp();
const plaintextCat = await readFile(CAT_PATH);
const ciphertextCat = await bootstrap.storeAttachmentOnCDN(
const ciphertextCat = await bootstrap.encryptAndStoreAttachmentOnCDN(
plaintextCat,
IMAGE_JPEG
);

View File

@@ -674,7 +674,7 @@ export class Bootstrap {
return join(this.#storagePath, 'attachments.noindex', relativePath);
}
public async storeAttachmentOnCDN(
public async encryptAndStoreAttachmentOnCDN(
data: Buffer,
contentType: MIMEType
): Promise<Proto.IAttachmentPointer> {
@@ -684,13 +684,13 @@ export class Bootstrap {
const passthrough = new PassThrough();
const [{ digest }] = await Promise.all([
const [{ digest, chunkSize, incrementalMac }] = await Promise.all([
encryptAttachmentV2({
keys,
plaintext: {
data,
},
needIncrementalMac: false,
needIncrementalMac: true,
sink: passthrough,
}),
this.server.storeAttachmentOnCdn(cdnNumber, cdnKey, passthrough),
@@ -703,6 +703,8 @@ export class Bootstrap {
cdnNumber,
key: keys,
digest,
chunkSize,
incrementalMac,
};
}

View File

@@ -6,6 +6,8 @@ import { assert } from 'chai';
import { expect } from 'playwright/test';
import { type PrimaryDevice, StorageState } from '@signalapp/mock-server';
import * as path from 'path';
import { readFile } from 'fs/promises';
import type { App } from '../playwright';
import { Bootstrap } from '../bootstrap';
import {
@@ -16,6 +18,8 @@ import {
} from '../helpers';
import * as durations from '../../util/durations';
import { strictAssert } from '../../util/assert';
import { VIDEO_MP4 } from '../../types/MIME';
import { toBase64 } from '../../Bytes';
export const debug = createDebug('mock:test:attachments');
@@ -27,6 +31,14 @@ const CAT_PATH = path.join(
'fixtures',
'cat-screenshot.png'
);
const VIDEO_PATH = path.join(
__dirname,
'..',
'..',
'..',
'fixtures',
'ghost-kitty.mp4'
);
describe('attachments', function (this: Mocha.Suite) {
this.timeout(durations.MINUTE);
@@ -121,4 +133,83 @@ describe('attachments', function (this: Mocha.Suite) {
assert.strictEqual(incomingAttachment?.key, sentAttachment?.key);
assert.strictEqual(incomingAttachment?.digest, sentAttachment?.digest);
});
it('can download videos with incrementalMac and is resilient to bad incrementalMacs', async () => {
const { desktop } = bootstrap;
const page = await app.getWindow();
await page.getByTestId(pinned.device.aci).click();
const plaintextVideo = await readFile(VIDEO_PATH);
const videoPointer1 = await bootstrap.encryptAndStoreAttachmentOnCDN(
plaintextVideo,
VIDEO_MP4
);
const videoPointer2 = await bootstrap.encryptAndStoreAttachmentOnCDN(
plaintextVideo,
VIDEO_MP4
);
const incrementalTimestamp = Date.now();
const badIncrementalTimestamp = incrementalTimestamp + 1;
await sendTextMessage({
from: pinned,
to: desktop,
desktop,
text: 'video with good incrementalMac',
attachments: [videoPointer1],
timestamp: incrementalTimestamp,
});
await sendTextMessage({
from: pinned,
to: desktop,
desktop,
text: 'video with bad incrementalMac',
attachments: [
{ ...videoPointer2, chunkSize: (videoPointer2.chunkSize ?? 42) + 1 },
],
timestamp: badIncrementalTimestamp,
});
await expect(
getMessageInTimelineByTimestamp(page, incrementalTimestamp).locator(
'img.module-image__image'
)
).toBeVisible();
await expect(
getMessageInTimelineByTimestamp(page, badIncrementalTimestamp).locator(
'img.module-image__image'
)
).toBeVisible();
// goodIncrementalMac preserved
{
const messageInDB = (
await app.getMessagesBySentAt(incrementalTimestamp)
)[0];
strictAssert(messageInDB, 'message exists in DB');
const attachmentInDB = messageInDB.attachments?.[0];
strictAssert(videoPointer1.incrementalMac, 'must exist');
strictAssert(videoPointer1.chunkSize, 'must exist');
assert.strictEqual(
attachmentInDB?.incrementalMac,
toBase64(videoPointer1.incrementalMac)
);
assert.strictEqual(attachmentInDB?.chunkSize, videoPointer1.chunkSize);
}
// badIncrementalMac removed
{
const messageInDB = (
await app.getMessagesBySentAt(badIncrementalTimestamp)
)[0];
strictAssert(messageInDB, 'message exists in DB');
const attachmentInDB = messageInDB.attachments?.[0];
strictAssert(videoPointer2.incrementalMac, 'must exist');
strictAssert(videoPointer2.chunkSize, 'must exist');
assert.strictEqual(attachmentInDB?.incrementalMac, undefined);
assert.strictEqual(attachmentInDB?.chunkSize, undefined);
}
});
});

View File

@@ -51,19 +51,19 @@ describe('attachment backfill', function (this: Mocha.Suite) {
const { unknownContacts } = bootstrap;
[unknownContact] = unknownContacts;
textAttachment = await bootstrap.storeAttachmentOnCDN(
textAttachment = await bootstrap.encryptAndStoreAttachmentOnCDN(
Buffer.from('look at this pic, it is gorgeous!'),
LONG_MESSAGE
);
const plaintextCat = await readFile(CAT_PATH);
catAttachment = await bootstrap.storeAttachmentOnCDN(
catAttachment = await bootstrap.encryptAndStoreAttachmentOnCDN(
plaintextCat,
IMAGE_JPEG
);
const plaintextSnow = await readFile(SNOW_PATH);
snowAttachment = await bootstrap.storeAttachmentOnCDN(
snowAttachment = await bootstrap.encryptAndStoreAttachmentOnCDN(
plaintextSnow,
IMAGE_JPEG
);