Allow incremental playback for attachments with plaintextHash

This commit is contained in:
trevor-signal
2026-03-24 17:23:02 -04:00
committed by GitHub
parent c353d41794
commit 3a25597873
2 changed files with 36 additions and 12 deletions

View File

@@ -25,6 +25,7 @@ import { access } from 'node:fs/promises';
import {
type DecryptAttachmentToSinkOptionsType,
decryptAttachmentV2ToSink,
type IntegrityCheckType,
} from '../ts/AttachmentCrypto.node.js';
import * as Bytes from '../ts/Bytes.std.js';
import type { MessageAttachmentsCursorType } from '../ts/sql/Interface.std.js';
@@ -68,6 +69,7 @@ import {
getStickersPath,
getTempPath,
} from './attachments.node.js';
import { isValidDigest, isValidPlaintextHash } from '../ts/types/Crypto.std.js';
const { isNumber } = lodash;
@@ -98,7 +100,7 @@ type RangeFinderContextType = Readonly<
}
| {
type: 'incremental';
digest: Uint8Array<ArrayBuffer>;
integrityCheck: IntegrityCheckType;
incrementalMac: Uint8Array<ArrayBuffer>;
chunkSize: number;
keysBase64: string;
@@ -154,10 +156,7 @@ async function safeDecryptToSink(
theirChunkSize: ctx.chunkSize,
theirIncrementalMac: ctx.incrementalMac,
type: 'standard',
integrityCheck: {
type: 'encrypted',
digest: ctx.digest,
},
integrityCheck: ctx.integrityCheck,
};
const controller = new AbortController();
@@ -703,9 +702,26 @@ export async function handleAttachmentRequest(req: Request): Promise<Response> {
// When trying to view in-progress downloads, we need more information
// to validate the file before returning data.
const digestBase64 = url.searchParams.get('digest');
if (digestBase64 == null) {
return new Response('Missing digest', { status: 400 });
const digestBase64 = url.searchParams.get('digest') ?? undefined;
const plaintextHashHex =
url.searchParams.get('plaintextHash') ?? undefined;
let integrityCheck: IntegrityCheckType;
if (isValidPlaintextHash(plaintextHashHex)) {
integrityCheck = {
type: 'plaintext',
plaintextHash: Bytes.fromHex(plaintextHashHex),
};
} else if (isValidDigest(digestBase64)) {
integrityCheck = {
type: 'encrypted',
digest: Bytes.fromBase64(digestBase64),
};
} else {
return new Response('Missing/invalid plaintextHash or digest', {
status: 400,
});
}
const incrementalMacBase64 = url.searchParams.get('incrementalMac');
@@ -724,7 +740,7 @@ export async function handleAttachmentRequest(req: Request): Promise<Response> {
context = {
type: 'incremental',
chunkSize,
digest: Bytes.fromBase64(digestBase64),
integrityCheck,
incrementalMac: Bytes.fromBase64(incrementalMacBase64),
keysBase64,
path,

View File

@@ -27,6 +27,7 @@ export function getLocalAttachmentUrl(
AttachmentType,
| 'contentType'
| 'digest'
| 'plaintextHash'
| 'downloadPath'
| 'incrementalMac'
| 'chunkSize'
@@ -87,10 +88,17 @@ export function getLocalAttachmentUrl(
}
url.searchParams.set('key', attachment.key);
if (!attachment.digest) {
throw new Error('getLocalAttachmentUrl: Missing attachment digest!');
// Attachments restored from backup / link & sync may have plaintextHash but not
// digest
if (attachment.plaintextHash) {
url.searchParams.set('plaintextHash', attachment.plaintextHash);
} else if (attachment.digest) {
url.searchParams.set('digest', attachment.digest);
} else {
throw new Error(
'getLocalAttachmentUrl: Missing attachment plaintextHash and digest!'
);
}
url.searchParams.set('digest', attachment.digest);
if (!attachment.incrementalMac) {
throw new Error(