diff --git a/DESIGN-web-app.md b/DESIGN-web-app.md new file mode 100644 index 0000000..6481f76 --- /dev/null +++ b/DESIGN-web-app.md @@ -0,0 +1,118 @@ +# Pithos in the browser (plan) + +Status: plan, 2026-10-05. Nothing built yet. + +## Goal + +Open your drives from any browser, on any device, with nothing installed: +browse, upload, download, rename, delete and share files on **Drives on +Silent Mode** (the hosted gateway at `https://s3.silentmode.st`). + +Out of scope: a drive on your own computer (`http://127.0.0.1:8000`). s3d +sends no CORS headers, so a web page cannot talk to it; for that the website +hands over to the local Pithos ("Open my Pithos", port 47621). + +## Where it lives: its own origin + +**`https://s3.silentmode.st/app`**, served by the hosted gateway. + +- Not on `navigate.st/bns/pithos.sia/app`: every BNS site served through + navigate.st shares the origin `navigate.st`, so any other `.sia`/`.bch` site + could open the app's window (same origin) and read the keys out of it, or + read anything it stored. +- Same origin as the API, so **no CORS** has to be opened on the gateway at + all, and the browser keeps the keys away from every other site. +- `pithos.sia/app` keeps its meaning: in Theseus it is the full app (the + add-on answers it); in any other browser the website's app-address hint + gets a third button, "Open in the browser app", linking to + `https://s3.silentmode.st/app`. +- Strict CSP (`default-src 'self'`, `connect-src 'self'`, no inline script, + `frame-ancestors 'none'`), no third-party scripts, files served by the + gateway from `Pithos/web/` (versioned with Pithos). + +## Signing in + +Two kinds of key, both loaded from a file (drag and drop or picker) or pasted: + +1. **Your Pithos key** (owner, full access, encrypted drives readable). + - Without Theseus: the existing export, `pithos-hosted-key.json` + (`{v:1, secret}`), Accounts › On Silent Mode › Export key. From the secret: + identity seed = HKDF(secret, "", "pithos/hosted-identity/v1", 32), + encryption master = HKDF(secret, "", "pithos/hosted-encryption/v1", 32). + - With Theseus there is no file: the keys come from the vault. New export + **"Key for the browser app"** (vault unlocked + Theseus approval prompt): + `{v:2, identitySeed, encryptionKey}`, the two 32-byte values derived from + the vault paths `pithos/hosted-identity/v1` and + `pithos/hosted-encryption/v1`. NEVER change those paths. The file is as + powerful as the vault for these drives; the dialog says so. + - Flow: `GET /hosted/v1/challenge` → Ed25519 signature in WebCrypto + (PKCS8 wrapper `302e020100300506032b657004220420 || seed`) → + `POST /hosted/v1/session` → Bearer token → `GET /api/keys` → the account's + S3 key → S3 calls signed in the page. +2. **An S3 key file** (the Export JSON format `{endpoint, accessKey, + secretKey, bucket}`), including folder keys (grant keys `PG…`) from Share…. + No encryption key: plain files and shared folders (stored readable) work, + encrypted names show as "encrypted file". The endpoint must be the + gateway. + +Keys live **in memory only** by default; closing the tab signs out. +Optional "Remember on this device": the key file is wrapped with a +passphrase (PBKDF2-SHA256 600k + AES-GCM, as Save credentials… does) in +IndexedDB on the gateway origin. Sign out wipes it and calls +`DELETE` on the session. + +## Crypto in the page + +Port `core/crypt.js` and the signing part of `core/s3.js` to browser ESM +in `Pithos/web/lib/`, sharing test vectors with the Node versions. + +- HKDF-SHA256, AES-256-GCM (bodies), AES-CTR, HMAC-SHA256 (SigV4): WebCrypto. +- AES-SIV (RFC 5297) for names is not in WebCrypto: + - CMAC/S2V from AES-CBC with a zero IV (WebCrypto appends a PKCS#7 + block: take the block before it); one-block AES-ECB the same way. + - SIV-CTR with AES-CTR `counter = V'` (bits cleared as crypt.js does), + `length: 128`. + - No library: @noble/ciphers' `siv` is AES-GCM-SIV (RFC 8452), not RFC 5297. +- Tests: RFC 5297 vectors, plus Node-encrypts / browser-decrypts and back for + names (with the bucket + parent-path AD) and bodies (chunk sizes, ranges, + empty file), run under Node's WebCrypto in `npm test`. + +## What the app does (v1) + +- Drives list, folder browsing (merged encrypted + plain listing, as + `core/hosted.js` does), icons/list views. +- Upload: drag and drop; encrypted chunk by chunk (1 MiB) in the page; + multipart above ~64 MiB so big files never sit in memory whole. +- Download: decrypt as a stream (File System Access API where available, + else a Blob; very large files on browsers without it get a warning). +- Rename, move, delete, new folder (renames re-encrypt, as the desktop does). +- Share: public folder / link / folder key through `/hosted/v1/rules`. +- Viewer for pictures and text, audio/video player using range reads. +- Free space and account state (`/hosted/v1/me`, `/api/space`). + +Later: the docked music player, offline cache, PWA install. + +## Gateway changes (Pithos/hosted) + +- Serve `GET /app`, `/app/*` (static files, strict CSP, long cache with + versioned names), before the S3/auth routing. +- Nothing else: same origin, so no CORS. The S3 path rules stay as they are + (owner keys raw-proxied to s3d; grant keys checked by `rules.js`). +- Rate limits already in place for sign-in apply. + +## Phases + +1. Crypto + SigV4 port with cross-tests (largest risk, done first). +2. Gateway `/app` route + sign-in with a key file + read-only browsing and + downloads. +3. Uploads, folders, rename/delete. +4. Sharing, viewer and player. +5. "Key for the browser app" export in Theseus; website button; docs. + +## Open questions + +- Is `s3.silentmode.st/app` the address, or a dedicated `pithos.silentmode.st` + (same design, another certificate and vhost)? +- Should the browser app also sign up new accounts (BNS signature / invite), + or only sign in to accounts made in Pithos? +- "Remember on this device": allow it, or always ask for the key file?