Developer Docs

Crypto โ€” Integration Guide

Overview

The crypto service is the platform's centralized encryption microservice โ€” a Rust TCP worker that provides AES-256-GCM file encryption for any backend consumer. Architecturally, it mirrors the imaging service: a dedicated TCP microservice that other services call for CPU-bound operations, keeping the main GraphQL subgraphs non-blocking.

The crypto service has zero knowledge of the files it encrypts. It is stateless โ€” callers manage their own keys. This design ensures that no single service holds both the ciphertext and the key, reinforcing the platform's E2EE security model.

Architecture at a Glance

Component Technology Port Access
Framework Rust (Tokio TCP) 30022 Internal only
Algorithm AES-256-GCM โ€” 256-bit key
Key Gen OS CSPRNG โ€” Via OsRng
Library ngwenya_crypto โ€” WASM-compilable

Why Rust? Crypto operations are CPU-bound โ€” Rust is ~10ร— faster than Node.js for AES-GCM. Rust's memory safety also eliminates buffer overflow vulnerabilities when handling key material. The library target (src/lib.rs) can compile to WASM for frontend reuse โ€” one implementation across the full stack.


Cross-Service Consumer Map

The crypto service is designed for cross-service reuse across the entire Ngwenya platform:

Consumer Use Case Status
uChat E2EE file attachments in encrypted conversations โœ… Live
uMail Encrypted email attachments ๐Ÿ“‹ Future
uCloud Private cloud file storage (25 MB+ files) ๐Ÿ“‹ Future
Webhooks Encrypted webhook payloads ๐Ÿ“‹ Future
Arcade Gaming Digital key encryption at rest ๐Ÿ“‹ Phase 2

uCloud: When a Visitor tries to send a file exceeding 25 MB via uChat, the error message directs them to uCloud Private Storage โ€” a future universal cloud service that will use this same crypto microservice for encryption.


TCP Protocol

The service accepts newline-delimited JSON over TCP. Each request is a single JSON object followed by \n, and the response follows the same format.

Encrypt File

// Request
{"action": "encrypt_file", "data": [104, 101, 108, 108, 111]}

// Response
{
  "ciphertext": [...],
  "key": "a1b2c3d4e5f6...",
  "iv": "d4e5f6...",
  "algorithm": "aes-256-gcm",
  "encrypted": true
}

The key and iv are hex-encoded for safe transport. An optional key field on the request allows callers to provide their own key instead of generating one.

Decrypt File

// Request
{"action": "decrypt_file", "ciphertext": [...], "key": "a1b2c3...", "iv": "d4e5f6..."}

// Response
{"data": [104, 101, 108, 108, 111], "decrypted": true}

Tamper Detection: If the ciphertext has been modified, decryption fails immediately with an authentication tag mismatch error. This is a security property of GCM mode โ€” the server cannot silently return corrupted data.

Generate Key

// Request
{"action": "generate_key"}

// Response
{"key": "a1b2c3d4e5f6...", "algorithm": "aes-256-gcm"}

Health Check

// Request
{"action": "health"}

// Response
{"status": "ok", "algorithm": "aes-256-gcm"}

Security Model

Property Value
Algorithm AES-256-GCM (authenticated encryption)
Key size 256 bits (32 bytes)
Nonce (IV) 96 bits (12 bytes), randomly generated per operation
Authentication tag 128 bits (appended to ciphertext by aes-gcm crate)
Key generation OS CSPRNG via OsRng โ€” suitable for production
Tamper detection GCM tag verification fails if ciphertext is modified
Key storage None โ€” the service is stateless, callers manage their own keys
Tag verification Constant-time (provided by the aes-gcm crate)

Key Interoperability

The crypto service uses the same algorithm (AES-256-GCM) as:

  • The frontend $lib/crypto module (Web Crypto API)
  • Malet Vaults (Node.js crypto module)

This means keys generated by any of these implementations can be used interchangeably. A file encrypted by the frontend can be decrypted by the backend service, and vice versa.


Frontend Integration: `$lib/crypto`

The frontend uses a shared $lib/crypto module (TypeScript) that mirrors the crypto service's strategy pattern โ€” identical to how the imaging service uses RemoteStrategy vs LocalStrategy.

Strategy Pattern

Strategy Environment Implementation
WebCryptoStrategy Production (browsers) AES-256-GCM via window.crypto.subtle
NoOpStrategy Dev/testing/SSR Passthrough (no encryption)
WasmStrategy Future Compile lib.rs to WASM

Usage

import { fileCrypto } from '$lib/crypto';

// Encrypt a file
const result = await fileCrypto.encrypt(fileBuffer);
// result.ciphertext โ†’ upload to S3
// result.key, result.iv โ†’ wrap in Megolm envelope via encrypted_body

// Decrypt a file
const plaintext = await fileCrypto.decrypt(ciphertext, key, iv);

Mobile Support

The Web Crypto API is available in:

  • iOS: WKWebView supports window.crypto.subtle
  • Android: WebView supports window.crypto.subtle

For future native (non-WebView) apps, the Rust lib.rs can compile to native libraries via cargo-ndk (Android) and as a static lib (iOS).


WASM Dual-Target Architecture

The src/lib.rs file is a library crate (ngwenya_crypto) that can be compiled to multiple targets:

graph TB
    A["src/lib.rs<br/>(ngwenya_crypto)"] --> B["Native Binary<br/>TCP Service (port 30022)"]
    A --> C["WASM Module<br/>Browser/Node.js"]
    A --> D["Android Native<br/>via cargo-ndk"]
    A --> E["iOS Static Lib<br/>via cargo-lipo"]
    
    B --> F["Backend Consumers<br/>uMail, Webhooks"]
    C --> G["Frontend $lib/crypto<br/>Replaces WebCryptoStrategy"]
    D --> H["Android App"]
    E --> I["iOS App"]

This architecture means a single Rust implementation powers:

  1. The TCP microservice (for backend consumers like uMail)
  2. The browser (via WASM, replacing WebCryptoStrategy)
  3. Native mobile apps (via cargo-ndk / static lib)

File Attachments Flow (uChat)

The crypto service is the backbone of uChat's file attachment system. Here's the complete lifecycle:

sequenceDiagram
    participant V as Visitor (Browser)
    participant FC as $lib/crypto
    participant M as Media Service
    participant S3 as S3 / CDN
    participant UC as uChat Mutation
    participant R as Redis Pub/Sub
    participant V2 as Recipient

    V->>FC: encrypt(fileBuffer)
    FC-->>V: {ciphertext, key, iv}
    V->>M: requestUploadUrl(filename, mime)
    M-->>V: {uploadUrl, fileId}
    V->>S3: PUT encrypted blob
    V->>M: confirmUpload(fileId)
    M-->>V: {cdnUrl}
    V->>UC: sendMessage(FILE, cdnUrl, encryptedBody)
    UC->>R: PUBLISH new_message + file_metadata
    R->>V2: WebSocket delivery
    V2->>S3: GET encrypted blob
    V2->>FC: decrypt(blob, key, iv)
    FC-->>V2: plaintext file

Security Boundaries

  1. Client-side encryption: The file is encrypted before leaving the browser
  2. Opaque storage: S3 stores an encrypted blob โ€” the storage provider cannot read the file
  3. Key wrapping: The AES key + IV are wrapped in the Megolm encrypted_body โ€” the uChat server stores this but cannot decrypt it
  4. Recipient decryption: Only the recipient's browser (with the Megolm session key) can extract the AES key and decrypt the file

Size Limit

Files are capped at 25 MB (26,214,400 bytes), enforced at the sendMessage mutation level. Files exceeding this limit receive:

"File exceeds 25 MB limit. Large file sharing will be available via uCloud Private Storage."

Environment Variables

Variable Required Default Description
CRYPTO_SERVICE_PORT_TCP No 30022 TCP listener port
RUST_LOG No crypto_service=info Log level filter

File Reference

File Purpose
apps/crypto/Cargo.toml Dependencies: aes-gcm, rand, tokio, serde
apps/crypto/src/lib.rs Core crypto primitives (library target, WASM-compilable)
apps/crypto/src/handler.rs TCP JSON-framed request/response handler
apps/crypto/src/main.rs TCP bootstrap entrypoint
apps/crypto/README.md Protocol docs, security model, multi-target notes

Frontend

File Purpose
src/lib/crypto/strategies/interface.ts FileCryptoStrategy interface
src/lib/crypto/strategies/webCrypto.ts Production AES-256-GCM via Web Crypto API
src/lib/crypto/strategies/noop.ts Dev/testing passthrough
src/lib/crypto/fileCrypto.ts Strategy factory (auto-selects per environment)
src/lib/crypto/index.ts Barrel export

Testing

Unit Tests

cd apps/crypto && cargo test

10 tests covering:

  • Key generation (length, uniqueness)
  • Encrypt/decrypt round-trip (with generated and provided keys)
  • Wrong key rejection (authentication tag mismatch)
  • Tampered ciphertext rejection
  • Large file round-trip (1 MB)
  • Empty file round-trip
  • Invalid hex input
  • Wrong key length

Integration Tests

cd apps/uchat && cargo test --test file_attachments

5 tests validating FILE message type:

  • Required field validation (file_url)
  • 25 MB size limit enforcement
  • TEXT message backward compatibility
  • Redis pub/sub payload structure