Handbuch Technik API Admin

Architektur-Uebersicht

PluriKey ist ein lokaler, verschluesselter Passwort-Manager. Drei Komponenten — Server, Extension und Desktop — teilen sich eine gemeinsame Rust-Codebasis.

┌─────────────────────────────────────────────────────────────┐ │ PluriKey Architektur │ ├──────────────┬──────────────────┬────────────────────────────┤ │ Extension │ Desktop │ Server │ │ (TypeScript)│ (Tauri 2.x) │ (Rust) │ │ │ │ │ │ Manifest V3 │ WebView │ Axum Web Framework │ │ Webpack │ + Embedded │ age Verschluesselung │ │ Content │ Axum Server │ Vault-Dateisystem │ │ Scripts │ Chrome-Shim │ WebAuthn (webauthn-rs) │ │ │ │ │ ├──────────────┴──────────────────┤ │ │ Gemeinsamer Frontend-Code │ plurikey-core (lib) │ │ (vault.ts, settings.ts) │ plurikey-server (lib+bin)│ └─────────────────────────────────┴────────────────────────────┘

Crate-Struktur

CrateTypZweck
plurikey-corelibVerschluesselung, Vault-Operationen, age-Integration, AI Pattern Detection
plurikey-serverlib + binAxum REST API, Session-Management, WebAuthn. Als Library nutzbar (start_server())
plurikey-desktopTauri AppDesktop-Wrapper: Embedded Axum Server + WebView Frontend
plurikey-extensionTypeScriptBrowser Extension: Content Scripts, Service Worker, Popup, Settings

Datenfluss

Alle Daten bleiben lokal. Der Server laeuft auf localhost (Port 8200 fuer Server-Modus, 8210 fuer Desktop).

  1. User gibt Passphrase ein → Extension/Desktop sendet POST /api/ext/auth/login
  2. Server leitet Passphrase durch scrypt → age Identity
  3. age entschluesselt Vault-Dateien → Eintraege im Speicher
  4. Extension empfaengt Token → alle weiteren Requests mit Authorization: Bearer
  5. Aenderungen werden sofort als verschluesselte .age-Dateien gespeichert

Verschluesselung mit age

PluriKey nutzt age (Actually Good Encryption) — ein modernes, schlankes Verschluesselungsformat:

  • Algorithmen: X25519 Diffie-Hellman + ChaCha20-Poly1305
  • Passphrase-Modus: scrypt Key Derivation (N=2^18, r=8, p=1)
  • Dateien: Jeder Eintrag ist eine einzelne .age-Datei — kein Datenbank-Blob
  • Format: Standard age-Format, kompatibel mit age CLI-Tool
Zero-Knowledge
Der Server speichert die Passphrase nie auf Disk. Die age Identity existiert nur im RAM waehrend die Session aktiv ist. Nach Logout oder Timeout wird sie verworfen.

Key Derivation

Passphrase
    │
    ▼
  scrypt (N=2^18, r=8, p=1)
    │
    ▼
  age Identity (X25519 Private Key)
    │
    ▼
  age Recipient (X25519 Public Key)
    │
    ▼
  Encrypt/Decrypt .age Dateien

Multi-Recipient Verschluesselung

age unterstuetzt mehrere Empfaenger pro Datei. PluriKey nutzt dies fuer:

  • Credential Sharing: Mehrere Benutzer koennen denselben Vault oeffnen
  • Recovery Keys: BIP39 Mnemonic generiert zusaetzlichen age Key
  • Future: Credential Proxying — zeitlich begrenzter Zugang ohne Passwort-Offenlegung

Extension: Manifest V3

Die Browser Extension nutzt Chrome Manifest V3 mit minimalen Permissions:

{
  "manifest_version": 3,
  "permissions": [
    "activeTab",     // Nur aktiver Tab, kein Zugriff auf alle Tabs
    "storage",       // chrome.storage.local fuer Settings
    "idle",          // Screen-Lock Erkennung
    "alarms"         // Auto-Lock Timer, Pending-Save Cleanup
  ]
}

Content Script

Der Content Script erkennt Login-Formulare und fuellt Credentials aus:

  • Feld-Erkennung ueber type="password", autocomplete="username", Heuristiken
  • Fill-Suggestions: Dropdown unter erkannten Feldern mit passenden Eintraegen
  • Kommunikation mit Service Worker via chrome.runtime.sendMessage()
Security: Kein CustomEvent!
Die Fill-Kommunikation zwischen Content Script und Field Suggestions nutzt einen direkten Callback (setFillHandler()), NICHT CustomEvent. CustomEvents koennen von Page-Scripts gespooft werden — ein Angriffsvektor, der bei Bitwarden dokumentiert wurde.

Service Worker

Der Service Worker (Background Script) verwaltet:

FunktionMechanismus
API-ClientZentrale REST-Kommunikation mit dem Server
Auto-Lock Timerchrome.alarms — sperrt Vault nach Inaktivitaet
Idle Monitorchrome.idle API — sperrt bei Bildschirmsperre
Token RefreshVerlaengert Session bei Aktivitaet (max_session_secs Grenze)
Clipboard CleanupLoescht kopierte Passwoerter nach Timeout
Pending-Save Guard5-Min Auto-Clear fuer haengende Speicher-Flags

Plugin-System

PluriKey hat ein Build-Zeit Plugin-System fuer Pro-Features:

// webpack.config.js
new DefinePlugin({
  __PRO__: JSON.stringify(process.env.PRO === 'true')
})

// Im Code
if (__PRO__) {
  import('./pro/register').then(m => m.registerProPlugins());
}
  • Plugin-API: src/core/plugin-api.ts — PluriKeyPlugin Interface, PluginContext
  • Registry: src/core/plugin-registry.ts — registerPlugin, Hook Dispatchers
  • Free Build: npm run build — kein Pro-Code im Bundle
  • Pro Build: npm run build:pro — mit src/pro/ Plugins

Desktop: Tauri 2.x

Die Desktop-App nutzt Tauri 2.x mit einem eingebetteten Axum Server:

// lib.rs (vereinfacht)
fn main() {
    tauri::Builder::default()
        .setup(|app| {
            let config = ServerConfig {
                port: 8210,
                vault_dir: data_dir.join("vaults"),
            };
            tokio::spawn(async move {
                start_server(config).await;
            });
            Ok(())
        })
        .run(tauri::generate_context!())
}

Chrome-Shim

Da die Desktop-App keinen Chrome-Browser hat, ersetzt ein Shim die chrome.* APIs:

Chrome APIDesktop-Ersatz
chrome.storage.locallocalStorage
chrome.tabs.create()window.open()
chrome.runtime.getURL()Relativer Pfad
chrome.runtime.sendMessage()Direkter fetch() an localhost
chrome.idleNicht verfuegbar (Tauri hat eigenes Idle)

Der gleiche Frontend-Code (vault.ts, settings.ts) wird via Webpack-Alias ext-src sowohl fuer Extension als auch Desktop gebaut.

Server: Axum

Der PluriKey Server nutzt Axum 0.7 mit Tower-Middleware:

  • Routing: /api/ext/ Prefix fuer alle Extension-Endpoints
  • Auth: Bearer Token in Authorization Header
  • Session: RwLock<HashMap<String, String>> fuer Passphrase-Cache im RAM
  • CORS: chrome-extension://, http://tauri.localhost, tauri://
  • Library-Modus: start_server(ServerConfig) fuer Embedding in Tauri

Vault-Struktur

/vaults/
  stefan/                    # Vault-Verzeichnis
    entry-a1b2c3.age         # Verschluesselter Eintrag
    entry-d4e5f6.age         # Jeder Eintrag = eine .age Datei
    .recipients              # age Public Keys (Multi-Recipient)
    .webauthn_credentials.json  # WebAuthn Registrierungen

Jeder Eintrag enthaelt Key-Value Paare im Klartext (nach Entschluesselung):

Title: GitHub Account
ShieldType: Login
URL: https://github.com/login
UserName: stlas
Password: ********
Notes: 2FA via Authenticator App

WebAuthn Quick-Unlock

Nach Bildschirmsperre kann der Vault per FIDO2-Key, Touch ID oder Windows Hello entsperrt werden, statt die Passphrase erneut einzugeben:

  • Server: webauthn-rs 0.5 Crate, 4 Endpoints (register-begin/finish, auth-begin/finish)
  • Extension: navigator.credentials.create() / .get()
  • rpID: "localhost" (IP-Adressen funktionieren nicht mit WebAuthn)
  • Credentials: .webauthn_credentials.json pro Vault-Verzeichnis