Spring Boot • PostgreSQL • Redis • AWS S3 • Ed25519 Auth

IronHold API

End-to-end encrypted file storage backend. The server stores only encrypted blobs and never has access to plaintext file content.

Every API on this page is live. Your demo account was auto-created when you loaded this page.


Demo Session

Initialising demo account…

Generating Ed25519 key pair


Architecture Overview

IronHold is built around two architectural principles: files never pass through the application server, and the server is cryptographically blind to file content. The Spring Boot backend orchestrates metadata, access control, and presigned URL generation. S3 receives raw bytes directly from the client. Encryption happens in the client before bytes leave the device; the server handles only ciphertext.

Upload Flow

Client ──POST /api/file/upload──────────► Spring Boot API { name, sizeBytes, mimeType, parentFolderId } Spring Boot API ──generatePresignedPut──────► AWS S3 Spring Boot API ──returns to client─────────► Client { fileId, presignedUrl } Client ──PUT bytes (NO Auth header)──► S3 directly file bytes NEVER touch Spring Boot Client ──POST /api/file/upload/complete► Spring Boot API { fileId, encryptionKeyForRecipients } Spring Boot API ──headObject check──────────► S3 marks DONE → updates quota

Sharing & Key-Wrapping Model

Alice uploads file └─► AES key generated client-side └─► file encrypted with AES key └─► ciphertext PUT directly to S3 Alice shares with Bob └─► fetches Bob's Ed25519 public key └─► wraps AES key with Bob's public key └─► POST /api/file/share { encryptedFileKey: wrappedAESKey } stored in FileShare.encryptedFileKey Bob downloads └─► GET /api/file/download → presignedGetUrl └─► receives presignedUrl + encryptedFileKey └─► unwraps AES key with his private key └─► fetches ciphertext from S3 └─► decrypts locally server never sees plaintext

API Reference & Playground

Click any endpoint to expand it. Authenticated endpoints automatically attach your demo JWT. All requests go to https://your-deployed-url.com.

Authentication

Session Management

Upload Demo

In a real IronHold client, files would be AES-256-GCM encrypted before Step 2. The server stores only encrypted bytes and cannot read file content. This demo uploads unencrypted for simplicity. Do not upload sensitive files.

Click to choose a file, or drag & drop

Max 5 MB for demo

File Operations

Folder Operations


Your Files


Security Model

What IronHold stores

  • Username (not linked to any real identity)
  • Ed25519 public key (base64 SPKI)
  • File metadata: name, MIME type, size, timestamp
  • S3 object key (path to the ciphertext blob)
  • Per-share wrapped encryption key (encryptedFileKey)
  • BCrypt-hashed recovery codes (irreversible)
  • Revoked JWT records (for explicit logout)
  • Audit log entries (who accessed what, when)

What IronHold never sees

  • Plaintext file content — bytes go Client → S3 directly
  • Passwords — authentication is passwordless (Ed25519 only)
  • AES encryption keys — exist only in client memory
  • Ed25519 private keys — never transmitted
  • Recovery codes in plaintext — stored as BCrypt hashes only

Presigned URL model

When a client requests a file upload or download, Spring Boot generates a time-limited presigned URL via the AWS SDK. The URL embeds SigV4 credentials scoped to a single S3 operation. File bytes transfer directly between the client and S3. The Spring Boot server is not in the data path. Even a fully compromised application server cannot intercept file content in transit.

When PUTting to a presigned URL, do not include an Authorization header. AWS S3 only allows one auth mechanism per request; mixing presigned query params with an Authorization header causes a "Only one auth mechanism allowed" error.

Per-recipient key wrapping

Files are encrypted client-side with a per-file AES-256-GCM key. When sharing, the sender wraps the symmetric key with each recipient's Ed25519 public key and sends the wrapped key to the server as encryptedFileKey. The server stores this opaque blob. Only the intended recipient, holding the matching private key, can unwrap the symmetric key and decrypt the file.

Threat model

  • Server compromise: attacker sees only ciphertext and wrapped keys — no plaintext
  • DB breach: metadata exposed; file content and private keys are not in the DB
  • JWT theft: JWT revoked on logout; key rotation invalidates all prior tokens via timestamp
  • S3 compromise: attacker gets ciphertext only — useless without recipient private keys
  • Lost private key: eight one-time BCrypt-hashed recovery codes allow re-keying

Ed25519 passwordless auth

No passwords are ever stored or transmitted. The client generates an Ed25519 key pair locally and registers the public key. To authenticate, the server issues a random nonce; the client signs it with the private key. The server verifies the signature and issues a JWT. The private key never leaves the client device.


Recovery Codes

At registration, IronHold generates eight one-time recovery codes and presents them to the user once. Each code is immediately BCrypt-hashed (cost factor 10) and stored server-side. The plaintext is discarded. If a user loses their Ed25519 private key, they can call POST /api/auth/recover with any unused recovery code and a new public key. The server verifies the plaintext against the BCrypt hash, invalidates that code, and issues a fresh JWT for the new key pair.

Recovery codes are single-use: once verified, the code is marked as used and can no longer be reused. The remaining codes stay valid. This mirrors the recovery code model used by TOTP-based 2FA. Store them offline in a password manager or print them and keep in a secure location.

The codes below are SAMPLE — NOT REAL. They are illustrative examples only.
7f3a9b2c-1e4d-4f8a-9c2b-3d7e1f5a8b9c
2b8f4e1a-7c3d-4a9f-8b2e-1c5d9f3a7b4e
9d1e6c3b-5f2a-4b8e-7d1c-4a9f2e6b8c3d
4a7f2d9e-8b1c-4e6f-a3d7-9c2b5e1f8a4d
c3b8e1f4-2d9a-4c7b-6e1f-3a8d5c9b2e7f
8e4b1d7f-3c9a-4f2e-b8c1-7d3e9a4f1b6c
1f9c4e8a-6b2d-4a3f-c9e1-5b8d2f7c4a1e
6d2a9f3e-1c8b-4d7a-e2f9-8b4c1d6a3e9f

8 codes generated per account  ·  BCrypt-hashed (cost factor 10)  ·  one-time use  ·  plaintext never stored server-side