Architektur-Uebersicht
PluriKey ist ein lokaler, verschluesselter Passwort-Manager. Drei Komponenten — Server, Extension und Desktop — teilen sich eine gemeinsame Rust-Codebasis.
Crate-Struktur
| Crate | Typ | Zweck |
|---|---|---|
plurikey-core | lib | Verschluesselung, Vault-Operationen, age-Integration, AI Pattern Detection |
plurikey-server | lib + bin | Axum REST API, Session-Management, WebAuthn. Als Library nutzbar (start_server()) |
plurikey-desktop | Tauri App | Desktop-Wrapper: Embedded Axum Server + WebView Frontend |
plurikey-extension | TypeScript | Browser 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).
- User gibt Passphrase ein → Extension/Desktop sendet
POST /api/ext/auth/login - Server leitet Passphrase durch scrypt → age Identity
- age entschluesselt Vault-Dateien → Eintraege im Speicher
- Extension empfaengt Token → alle weiteren Requests mit
Authorization: Bearer - 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
ageCLI-Tool
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()
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:
| Funktion | Mechanismus |
|---|---|
| API-Client | Zentrale REST-Kommunikation mit dem Server |
| Auto-Lock Timer | chrome.alarms — sperrt Vault nach Inaktivitaet |
| Idle Monitor | chrome.idle API — sperrt bei Bildschirmsperre |
| Token Refresh | Verlaengert Session bei Aktivitaet (max_session_secs Grenze) |
| Clipboard Cleanup | Loescht kopierte Passwoerter nach Timeout |
| Pending-Save Guard | 5-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— mitsrc/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 API | Desktop-Ersatz |
|---|---|
chrome.storage.local | localStorage |
chrome.tabs.create() | window.open() |
chrome.runtime.getURL() | Relativer Pfad |
chrome.runtime.sendMessage() | Direkter fetch() an localhost |
chrome.idle | Nicht 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
AuthorizationHeader - 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.5Crate, 4 Endpoints (register-begin/finish, auth-begin/finish) - Extension:
navigator.credentials.create()/.get() - rpID:
"localhost"(IP-Adressen funktionieren nicht mit WebAuthn) - Credentials:
.webauthn_credentials.jsonpro Vault-Verzeichnis