Data Flows and Security
SecuFile — Data Flows and Security
Data Flows and Security
Data Flows and Security
Encrypted Upload Flow (E2E)
End-to-end encryption is one of SecuFile's core features. Here is the detailed flow of a client-side encrypted upload:
1. Browser (React)
|-- Generates a random AES-256 key (Data Encryption Key = DEK)
|-- Encrypts the file in chunks with AES-256-GCM
| |-- Each chunk uses a derived nonce: baseNonce XOR chunkIndex
| |-- Optimal chunk size calculated based on file size:
| | - < 10 MB: 1 MB chunks
| | - 10-100 MB: 5 MB chunks
| | - > 100 MB: 10 MB chunks
|-- Retrieves the organization's RSA-4096 public key via the API
|-- Encrypts the DEK with RSA-OAEP (SHA-256)
|-- Sends to the server:
|-- The encrypted file (binary)
|-- The encrypted DEK (encrypted_key)
|-- Encryption metadata (nonce, chunk sizes, version)
|-- SHA-256 checksum of the original file
2. Go Backend (API)
|-- Receives the encrypted file + metadata
|-- Direct streaming to OVH S3 (no in-memory buffering)
|-- Stores metadata in PostgreSQL:
|-- encrypted_key, key_version, encryption_metadata (JSONB)
|-- encryption_type = "client", uses_custom_key = true
|-- original_checksum for verification
3. OVH S3
|-- Stores the encrypted file
|-- The server never has access to plaintext dataDecrypted Download Flow
1. Browser (React)
|-- Requests file download via GET /api/v1/clients/:id/files/:id/download
|-- If uses_custom_key = true (client-side encrypted):
| |-- Receives the encrypted file + metadata headers
| |-- Retrieves the organization's RSA-4096 private key
| |-- Unwraps the DEK using RSA-OAEP (SHA-256)
| |-- Decrypts the file chunk by chunk in the browser
| |-- Provides the decrypted file for download
|-- If uses_custom_key = false (server-side encrypted):
|-- The server decrypts the file before sending
|-- The file is sent in plaintext (over TLS)
2. Go Backend (for server-side decryption)
|-- Loads encrypted DEK from PostgreSQL
|-- Decrypts DEK via KMS (OVH KMS or local)
|-- Downloads encrypted file from S3
|-- Decrypts file with DEK (AES-256-GCM)
|-- Streams decrypted file to clientShare Link Flow
1. Share Link Creation (authenticated user)
|-- POST /api/v1/clients/:client_id/share-links
|-- Generates a secure random token (64 hex characters)
|-- Stores link configuration (password, expiry, max downloads)
|-- Returns public URL: https://secufile.secuaas.dev/s/{token}
2. Share Link Access (anonymous user)
|-- GET /s/{token} -> Returns link info (type, name, consent required)
|-- If consent text configured:
| |-- GET /s/{token}/consent-text -> Returns Loi 25 consent text
| |-- POST /s/{token}/accept-consent -> Records acceptance in audit trail
|-- If password protected:
| |-- Password validation required before access
|-- For download links:
| |-- GET /s/{token}/download -> File download
| |-- If E2E encrypted: encryption info in headers for client-side decryption
|-- For upload links:
| |-- POST /s/{token}/upload -> File upload (25 MB max)
| |-- Files stored in the organization's S3 bucket
3. Audit Trail
|-- Every action logged: access, download, upload, consent acceptanceKey Management Flow
Organization Creation:
|-- Generate RSA-4096 key pair (PKCS1 private, PKIX public)
|-- Store public key as PEM in PostgreSQL (organizations.public_key)
|-- Encrypt private key via KMS WrapKey
|-- Store encrypted private key as Base64 in PostgreSQL (organizations.encrypted_private_key)
File Upload (client-side E2E):
|-- Browser generates random AES-256 DEK
|-- Browser encrypts file with DEK (AES-256-GCM, chunked)
|-- Browser encrypts DEK with org's RSA public key (RSA-OAEP SHA-256)
|-- Server stores encrypted DEK in PostgreSQL (files.encrypted_key)
File Download (client-side E2E):
|-- Server returns encrypted file + encrypted DEK
|-- Browser retrieves org's encrypted private key
|-- Browser requests KMS UnwrapKey to decrypt private key
|-- Browser decrypts DEK with RSA private key
|-- Browser decrypts file with DEK
File Upload/Download (server-side):
|-- Server generates DEK
|-- Server encrypts file with DEK (AES-256-GCM)
|-- Server encrypts DEK with KMS WrapKey
|-- For download: reverse process (KMS UnwrapKey -> DEK -> decrypt file)Storage Manager Architecture
The StorageManager provides per-organization storage isolation:
StorageManager
|-- defaultStorage (S3Storage) -> Global fallback S3 bucket
|-- orgStorages map[UUID]*S3Storage -> Per-organization dedicated buckets
|-- clientStorages map[UUID]*S3Storage -> Legacy per-client buckets
|
|-- GetStorage(id) -> Returns org bucket if exists, else default
|-- RegisterClientStorage(config) -> Creates new S3 connection
|-- StartPeriodicReload(ctx, 30s) -> Reloads configs for multi-pod supportBucket naming convention: secufile_{env}_{msp-id}_{org-id}
Each organization's bucket is auto-provisioned on creation via the OVH Cloud API with dedicated credentials (access key + secret key) encrypted and stored in PostgreSQL.