Zum Inhalt springen
Docs

Befehlspalette

Zu einer Seite oder Ansicht springen oder ein Todo per Name finden.

Änderungen

Wundklar · share links

The zero-knowledge path from the nurse's device to the doctor's browser: the server stores ciphertext and a password hash, never the key.

Markdown

A share link lets a nurse send a prescribing doctor a one-off, time-bounded view of a report without a KIM address. The server only sees ciphertext and an Argon2id hash of the password; the link key is composed of a 128-bit fragment in the URL # and an 8-character password the nurse hands over out of band. Decision: ADR-0018; code: packages/api/src/features/share-links and the public portal route r.$linkId.

Key

link key = HKDF-SHA256(ikm = fragment (16 B, URL #k=) ‖ password (8 chars),
                      salt = linkId, info = "wundklar/share-link/v1")

The nurse's device builds the encrypted bundle wk-share-v1 (AES-GCM(link key, { manifest, pdf, photoBefore, photoAfter })) and uploads it to orgs/<org>/links/<linkId>.bin. The server stores the ciphertext object and a PHC Argon2id hash of the password; it never persists the password or the fragment. The fragment and the password live only in the creating device's link vault (local, encrypted under the device's local key, never synced) so a new password can only be set from that device; otherwise the owner revokes the link and creates a new one.

Opening

  1. The doctor opens the URL .../r/<linkId>#k=<fragment> in a clean profile. The browser reads the fragment from the hash — the hash is never sent to the server.
  2. The password gate posts the password to shareLinks.open. The server checks the PHC hash with Argon2id; a wrong password counts against the per-link counter.
  3. On a match the server returns the signed GET URL of the ciphertext bundle. The browser derives the link key from fragment + password, decrypts in memory and renders D-Doctor from the manifest.

Rate limits: 3 failures → 15 min lock, 5 failures → revoke and delete the ciphertext (not just the index row). All opens, failures and revocations are recorded in link_access with city, country, browser and the hashed IP.

Lifecycle

  • Creation on the device (M-RepLink) sets validity 1, 7 or 14 days from LINK_VALIDITY_DAYS and records the recipient label (doctor, Wundnetz).
  • The server never signs a GET for a revoked, expired or locked link.
  • shareLinksExpire runs every tick, revokes expired links and deletes their R2 object; the counter stays for the access log.
  • The owner can revoke a link manually; the ciphertext is deleted synchronously and the trail records the reason.

What the server cannot do

  • Decrypt an opened link's content: the fragment never reaches it.
  • Replay a bundle at a different URL: the manifest binds linkId as AAD and the doctor's browser refuses mismatches.
  • Guess passwords offline: only the PHC hash is stored, and the ciphertext is deleted after five wrong tries.

Reports delivered over KIM or printed never go through this path; see reports.

Auf GitHub bearbeitenDie Dokumentation ist auf Englisch.