Athena — auditPic/architecture.md

AuditPic — Architecture

Overview

AuditPic is a photo authenticity verification system. Auditors use the Flutter app to capture photos that are cryptographically signed at capture time. Anyone can later verify that a photo has not been altered, either by scanning a QR code or visiting the public viewer.

System Components

┌──────────────────────────────────────────────────────────────────────┐
│                        Mobile App (Flutter)                          │
│                                                                      │
│  CaptureScreen ──► ImageHashUtil (SHA-256) ──► PhotoApi (Dio)        │
│  GalleryScreen ──► PhotoApi  (QR code per photo, share/lock buttons) │
│  VerifyScreen  ──► PhotoApi + ImageHashUtil                          │
└────────────────────────────┬─────────────────────────────────────────┘
                             │ HTTPS REST + JWT
                             ▼
┌──────────────────────────────────────────────────────────────────────┐
│                     Backend (Spring Boot 3)                          │
│                                                                      │
│  POST /api/v1/photos                                                 │
│    ├── FieldEncryptionService  (AES-256-GCM — verify client SHA-256) │
│    ├── SigningService          (HMAC-SHA256 versioned signing)        │
│    ├── MinioStorageService     (upload original bytes)               │
│    └── AiDetectionService      (async AI manipulation check)         │
│                                                                      │
│  GET  /api/v1/photos/{id}         (metadata; 404 for private)        │
│  GET  /api/v1/photos/{id}/verify-signature                           │
│  GET  /api/v1/photos/{id}/download  (presigned MinIO URL, owner)     │
│  GET  /api/v1/photos              (authenticated list)               │
│  PATCH /api/v1/photos/{id}/visibility  (premium-only for private)    │
│  DELETE /api/v1/photos/{id}       (owner only)                       │
│                                                                      │
│  GET  /public/photos/{id}         (HTML viewer, rate-limited)        │
│  GET  /public/photos/{id}/image   (watermarked PNG, rate-limited)    │
│    └── WatermarkService  (LSB steganography, embeds verificationId)  │
│                                                                      │
│  POST /api/v1/auth/register|login|refresh                            │
└────────────┬──────────────────────────┬─────────────────────────────┘
             │                          │
             ▼                          ▼
    ┌─────────────────┐        ┌──────────────────┐
    │   PostgreSQL 15  │        │      MinIO       │
    │   (metadata,     │        │  (photo bytes,   │
    │  email encrypted)│        │  original only)  │
    └─────────────────┘        └──────────────────┘

Data Flow — Capture

  1. User takes a photo on device (camera-only, gallery upload blocked)
  2. Flutter computes SHA-256 of raw image bytes (ImageHashUtil.sha256Hex)
  3. Flutter records device timestamp (UTC ISO-8601)
  4. Flutter sends multipart/form-data to POST /api/v1/photos:
    • file — raw image bytes
    • sha256 — hex hash computed on device
    • capturedAt — device timestamp
  5. Backend recomputes SHA-256 from received bytes and rejects if mismatched (FIND-04 fix)
  6. Backend uploads image to MinIO under photos/{userId}/{uuid}.jpg
  7. Backend signs {sha256}:{signedAt.epochMilli} with HMAC-SHA256 using the current versioned key
  8. Backend writes a photos row to PostgreSQL (email encrypted at rest via AES-256-GCM)
  9. Backend asynchronously calls AI detection API (if enabled)
  10. Backend returns { verificationId, capturedAt, signedAt, sha256 } to Flutter
  11. Flutter generates QR code encoding https://api.auditpic.com/public/photos/{verificationId}

Data Flow — Public Verification

  1. Anyone scans the QR code → browser opens GET /public/photos/{verificationId}
  2. Backend fetches photo metadata and verifies the HMAC signature
  3. Backend streams the photo from MinIO through WatermarkService (LSB watermark embedded)
  4. Browser renders the HTML viewer: watermarked image + signature badge + AI result + metadata

Data Flow — In-App Verification

  1. User enters a verificationId (or scans QR) in the Verify screen
  2. Flutter calls GET /api/v1/photos/{verificationId}/verify-signature{ valid: true/false }
  3. (Optional) User picks a local copy of the suspect photo
  4. Flutter computes SHA-256 of the local photo and compares to metadata.sha256

Technology Choices

| Concern | Choice | Reason | |---|---|---| | Mobile | Flutter 3.8+ | Cross-platform (iOS + Android) | | Backend | Spring Boot 3.3.5 / Java 17 | Standard workspace stack | | Database | PostgreSQL 15 | Relational, Flyway migrations | | Object storage | MinIO (local) / Firebase Storage (production) | S3-compatible, self-hostable | | Auth | JWT (JJWT 0.12.6), self-issued | Stateless, no Firebase dependency | | Signing | HMAC-SHA256 (versioned keys) | Fast, server-side, rotation-safe | | AI detection | Hive Moderation | Deepfake/AI-generated image detection | | PII encryption | AES-256-GCM | Email encrypted at rest; emailHash for lookup | | Watermarking | LSB steganography | Invisible, lossless for PNG |

Security Architecture

PII Encryption (AES-256-GCM)

User.email is stored encrypted in the database. User.emailHash (HMAC-SHA256 of the plaintext email) is stored separately for indexed lookup. Key: FIELD_ENCRYPTION_KEY (32-byte base64), required in all non-local environments.

HMAC Key Rotation

Photo signatures use versioned HMAC-SHA256 keys. Each photos row stores hmac_key_version so old signatures remain verifiable during rotation. Automated quarterly rotation via .github/workflows/rotate-hmac.yml.

See CLAUDE.md for full rotation procedure.

Rate Limiting

RateLimitInterceptor implements a sliding-window rate limiter per IP:

  • Login: 5 req/min
  • Register: 3 req/min
  • Public viewer: 30 req/min

X-Forwarded-For is only trusted when the direct connection comes from an RFC-1918 address (k8s ingress proxy). Public IPs cannot spoof the client IP.

DB User Separation

  • Flyway user (auditpic): DDL privileges (CREATE TABLE, ALTER, etc.) — migration only
  • App user (auditpic_app): DML only (SELECT/INSERT/UPDATE/DELETE) — runtime

HTTP Security Headers

All responses from the backend include:

  • Strict-Transport-Security: max-age=31536000; includeSubDomains
  • Public viewer adds: Content-Security-Policy, X-Frame-Options: DENY

CI/CD

feature/* ──PR──► develop ──CI green──► CD: staging (namespace: audit-pic-staging)
develop   ──PR──► main    ──CI green──► CD: production (namespace: audit-pic)

CI checks (all required): Backend unit tests (118), Flutter analyze+test, PR title (Conventional Commits), branch targeting (feature/* → develop), PR description.

Maestro Android UI tests run as non-blocking (emulator flakiness on hosted runners).

Secrets flow: 1Password → load-secrets-actionkubeseal (Bitnami Sealed Secrets) → k3s

Flyway Migrations

| Version | Description | |---------|-------------| | V1 | Initial schema (photos, users) | | V2 | AI detection status enum | | V3 | Refresh tokens | | V4 | Photo retention days | | V5 | HMAC key version column on photos | | V6 | Photo visibility (is_public, is_premium) | | V7 | App role — auditpic_app DML-only | | V8 | Email AES-256-GCM encryption (email_hash lookup) |

Reacties

Nog geen reacties