Skip to content

Phase 4: Download & Decryption (Client-Side File Decryption) #176

@kevalyq

Description

@kevalyq

📌 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


📝 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)

⚠️ Important:

  • 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

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    ✅ Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions