add optional Argon2id password wrap for zk uploads.
Wrap the image DEK for password-protected batches, keep the URL fragment as a separate secret, and extend prepare/decrypt helpers with tests.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { base64ToBytes } from '@/utils/encoding/base64ToBytes';
|
||||
import { buildZkViewerPath } from '@/utils/share/buildZkViewerPath';
|
||||
import { decryptPasswordProtectedZkUpload } from '@/utils/upload/decryptPasswordProtectedZkUpload';
|
||||
import { decryptZkUpload } from '@/utils/upload/decryptZkUpload';
|
||||
import { prepareZkUploadRequest } from '@/utils/upload/prepareZkUploadRequest';
|
||||
|
||||
@@ -58,4 +59,54 @@ describe('prepareZkUploadRequest', () => {
|
||||
|
||||
expect(recovered).toEqual(plaintext);
|
||||
});
|
||||
|
||||
describe('password protected', () => {
|
||||
const passwordOptions = {
|
||||
expiresAt: null,
|
||||
maxViews: null,
|
||||
passwordProtected: true as const,
|
||||
password: 'batch-secret'
|
||||
};
|
||||
|
||||
it('includes wrap fields and omits password from the POST body', async () => {
|
||||
const { request, fragmentKey } = await prepareZkUploadRequest(new Uint8Array([1]), passwordOptions);
|
||||
|
||||
expect(request.passwordProtected).toBe(true);
|
||||
expect(request.wrappedDek).toMatch(/^[A-Za-z0-9+/]+=*$/);
|
||||
expect(request.wrapIv).toMatch(/^[A-Za-z0-9+/]+=*$/);
|
||||
expect(request.salt).toMatch(/^[A-Za-z0-9+/]+=*$/);
|
||||
expect(request).not.toHaveProperty('password');
|
||||
expect(fragmentKey).toMatch(/^[A-Za-z0-9_-]+$/);
|
||||
});
|
||||
|
||||
it('does not decrypt with URL fragment alone', async () => {
|
||||
const plaintext = new TextEncoder().encode('protected');
|
||||
const { request, fragmentKey } = await prepareZkUploadRequest(plaintext, passwordOptions);
|
||||
|
||||
await expect(
|
||||
decryptZkUpload(
|
||||
base64ToBytes(request.ciphertext),
|
||||
base64ToBytes(request.iv),
|
||||
fragmentKey
|
||||
)
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('roundtrips with fragment key and password', async () => {
|
||||
const plaintext = new TextEncoder().encode('protected-payload');
|
||||
const { request, fragmentKey } = await prepareZkUploadRequest(plaintext, passwordOptions);
|
||||
|
||||
const recovered = await decryptPasswordProtectedZkUpload(
|
||||
base64ToBytes(request.ciphertext),
|
||||
base64ToBytes(request.iv),
|
||||
fragmentKey,
|
||||
passwordOptions.password,
|
||||
base64ToBytes(request.wrappedDek!),
|
||||
base64ToBytes(request.wrapIv!),
|
||||
base64ToBytes(request.salt!)
|
||||
);
|
||||
|
||||
expect(recovered).toEqual(plaintext);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user