-
Notifications
You must be signed in to change notification settings - Fork 0
Closed
Description
📌 Parent Epic
#143 (Client-Side File Encryption for Zero-Knowledge Architecture)
🎯 Goal
Download encrypted files from backend and decrypt client-side for preview/download, including checksum verification and tampering detection.
📋 Implementation Tasks
1. Decryption Tests (Write FIRST)
File: src/services/secretApi.test.ts (extend)
- Test: Download and decrypt file
- Test: Verify checksum after decryption
- Test: Reject tampered files
- Test: Handle decryption errors gracefully
- Test: Restore original filename and MIME type
- Test: Handle missing files (404)
- Test: Handle network errors during download
2. Download & Decryption Functions
File: src/services/secretApi.ts (extend)
-
downloadAndDecryptAttachment(attachmentId: string, secretKey: CryptoKey): Promise<File>- Download encrypted blob + metadata from backend
- Derive file key (same as encryption)
- Decrypt blob
- Verify checksum (SHA-256)
- Restore original filename and MIME type
- Throw on checksum mismatch (tampering)
-
listAttachments(secretId: string): Promise<Attachment[]>- Fetch attachment list for Secret
- Return metadata only (no decryption)
3. Preview/Download UI
File: src/components/AttachmentList.tsx (new)
- Display attachment list for Secret
- Thumbnail preview for images (decrypted)
- Download button (triggers decrypt + download)
- Delete button (with confirmation)
- Loading indicators during decrypt
File: src/components/AttachmentPreview.tsx (new)
- Image preview (Base64 data URL)
- PDF preview (iframe or PDF.js)
- Document preview (name + icon)
- "Download original" button
4. Integration with Secret Detail Page
File: src/pages/SecretDetail.tsx (extend)
- Add "Attachments" section
- Display
<AttachmentList />component - Handle attachment actions (preview, download, delete)
✅ Acceptance Criteria
- Encrypted files downloaded from backend
- Decryption works correctly (roundtrip complete)
- Checksum verification enforced
- Tampering detected and rejected
- Preview works for images
- Download works for all file types
- Original filename and MIME type restored
- All tests passing (≥8 new tests)
- Code coverage ≥80% for new code
- TypeScript strict mode clean
- ESLint clean
- REUSE compliant (SPDX headers)
- Accessibility (a11y) compliant
🧪 Testing Strategy
describe('Secret API - Encrypted File Download', () => {
it('should download and decrypt file', async () => {
const attachmentId = 'attachment-123';
const secretKey = await generateTestKey();
const decryptedFile = await downloadAndDecryptAttachment(attachmentId, secretKey);
expect(decryptedFile).toBeInstanceOf(File);
expect(decryptedFile.name).toBe('document.pdf');
});
it('should verify checksum after decryption', async () => {
const result = await downloadAndDecryptAttachment('id', secretKey);
expect(result.checksumValid).toBe(true);
});
it('should reject tampered files', async () => {
const tamperedBlob = createTamperedBlob();
await expect(downloadAndDecryptAttachment('id', secretKey))
.rejects.toThrow('Checksum verification failed');
});
});🔗 Dependencies
- Depends on: #TBD (Phase 3 - Upload Integration)
- Blocks: #TBD (Phase 5 - Security Audit)
- Part of: Epic [EPIC] Client-Side File Encryption for Zero-Knowledge Architecture #143
📝 Technical Notes
Backend API Endpoints
GET /api/v1/attachments/{attachmentId}/download
Authorization: Bearer <JWT>
Response:
{
"encryptedBlob": "<base64 or binary>",
"metadata": {
"filename": "document.pdf",
"type": "application/pdf",
"size": 1024,
"encryptedSize": 1280,
"checksum": "abc123...",
"checksumEncrypted": "def456..."
}
}
Download & Decrypt Implementation
export async function downloadAndDecryptAttachment(
attachmentId: string,
secretKey: CryptoKey
): Promise<File> {
// 1. Download encrypted blob + metadata
const response = await fetch(`/api/v1/attachments/${attachmentId}/download`, {
headers: getAuthHeaders()
});
if (!response.ok) throw new Error('Download failed');
const { encryptedBlob, metadata } = await response.json();
// 2. Derive file key (same as encryption)
const fileKey = await deriveFileKey(secretKey, metadata.filename);
// 3. Parse encrypted blob
const blob: EncryptedFileBlob = parseEncryptedBlob(encryptedBlob);
// 4. Decrypt
const decryptedFile = await decryptFile(blob, fileKey);
// 5. Verify checksum
const checksum = await calculateChecksum(decryptedFile);
if (checksum !== metadata.checksum) {
throw new Error('Checksum verification failed (file corrupted or tampered)');
}
// 6. Restore original filename and MIME type
return new File([decryptedFile], metadata.filename, { type: metadata.type });
}Image Preview
// Decrypt and create data URL for preview
async function createImagePreview(attachmentId: string, secretKey: CryptoKey): Promise<string> {
const decryptedFile = await downloadAndDecryptAttachment(attachmentId, secretKey);
// Convert to Base64 data URL
const reader = new FileReader();
return new Promise((resolve, reject) => {
reader.onload = () => resolve(reader.result as string);
reader.onerror = reject;
reader.readAsDataURL(decryptedFile);
});
}🎨 UI/UX Design
┌─────────────────────────────────────────┐
│ Secret: Project Documents │
├─────────────────────────────────────────┤
│ Attachments: │
│ │
│ ┌─────────────────────────────────┐ │
│ │ 📄 contract.pdf 1.2 MB │ │
│ │ [Preview] [Download] [Delete] │ │
│ └─────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────┐ │
│ │ 🖼️ photo.jpg 500 KB │ │
│ │ [Thumbnail Preview] │ │
│ │ [Download] [Delete] │ │
│ └─────────────────────────────────┘ │
│ │
│ [+ Add Attachment] │
└─────────────────────────────────────────┘
🚀 Branch Strategy
# Create feature branch FROM Phase 3 branch
git checkout feat/upload-integration-phase3
git checkout -b feat/download-decryption-phase4
# Follow TDD cycle
git commit -S -m "test: add download/decrypt tests (Phase 4.1)"
git commit -S -m "feat: implement download and decryption (Phase 4.2)"
git commit -S -m "feat: add attachment preview UI (Phase 4.3)"
git commit -S -m "feat: integrate with Secret detail page (Phase 4.4)"📏 PR Linking Instructions
When creating the PR for this sub-issue, use this in your PR description:
Fixes #TBD
Part of: #143
Depends on: #TBD (Phase 3)- Wait for Phase 3 PR to be merged before starting
- Do NOT use
Fixes #143- this is not the last sub-issue - PR size: ~400-500 LOC (UI + API + tests)
Type: Sub-Issue (Phase 4/5)
Priority: High
Estimated Effort: 2 days
Sprint: Download & Decryption
Metadata
Metadata
Assignees
Labels
No labels
Type
Projects
Status
✅ Done