This document describes PassKey's solution structure, dependency graph, and key design patterns.
PassKey.sln
├── src/
│ ├── PassKey.Core/ # Domain models, crypto, services
│ ├── PassKey.Desktop/ # WinUI 3 app (MVVM, DI)
│ ├── PassKey.BrowserHost/ # Native Messaging bridge
│ └── PassKey.Tests/ # xUnit test suite
└── extensions/
├── chrome/ # Chrome Manifest V3 extension
└── firefox/ # Firefox Manifest V3 extension
- Type: Class library (.NET 10, AOT-ready)
- Purpose: All domain logic with zero UI dependency
- Contains: Models, crypto service, vault service, importers, backup service, password analyser, URL matcher, card type detector
- Dependencies:
Konscious.Security.Cryptography.Argon2,System.Security.Cryptography,Microsoft.Data.Sqlite(via interface)
- Type: WinUI 3 application (Windows App SDK 1.8, unpackaged, self-contained x64)
- Purpose: User interface and platform integration
- Contains: Views (XAML + code-behind), ViewModels, Services, Controls, Converters
- Dependencies: PassKey.Core, CommunityToolkit.Mvvm, CommunityToolkit.WinUI, Microsoft.WindowsAppSDK
- Type: Console application (.NET 10, single-file publish, AOT-ready)
- Purpose: Bridge between browser extensions and PassKey Desktop
- Protocol: Reads/writes Native Messaging format (4-byte length prefix + JSON) on stdin/stdout; connects to PassKey Desktop via Named Pipe
- Dependencies: PassKey.Core (models only)
- Type: xUnit test project
- Purpose: 167+ deterministic tests covering crypto, vault, importers, URL matching, card detection
- Dependencies: PassKey.Core (with
InternalsVisibleTo), PassKey.BrowserHost (withInternalsVisibleTo), Moq
PassKey.Desktop ────► PassKey.Core
▲
PassKey.BrowserHost ────────┘
▲
PassKey.Tests ──────────────┘
PassKey.Core has no dependency on any UI framework. PassKey.Desktop and PassKey.BrowserHost both depend on Core. PassKey.Tests references Core and BrowserHost.
PassKey uses a ViewModel-first navigation pattern:
ShellViewModelmanages the current page via aCurrentViewModelproperty.- Navigation sets
CurrentViewModelto a new ViewModel instance. MainWindowuses aContentControlbound toCurrentViewModel, withDataTemplateselectors mapping each ViewModel type to its corresponding View.- Views receive their ViewModel via
SetViewModel()in code-behind and setDataContext.
Benefits:
- ViewModels are fully unit-testable (no XAML dependency).
- Navigation logic lives in ViewModels, not code-behind.
All services are registered in the DI container at startup (App.xaml.cs). Dependencies are injected via constructor parameters.
Rules:
- No Service Locator pattern (never resolve from
App.Currentor a static container). - No
[Inject]attributes — constructor parameters only. - Scoped/transient where appropriate, singletons for long-lived services.
WinUI 3 does not allow multiple ContentDialog instances to be open simultaneously. PassKey uses a DialogQueueService with a Queue<Func<Task>> and a serial pump:
- Code enqueues a dialog request (a
Func<Task>that shows and awaits aContentDialog). - The pump dequeues and executes one at a time.
- The next dialog is shown only after the previous one is dismissed.
Why not SemaphoreSlim? Using
SemaphoreSlim.WaitAsync()on the UI thread causes deadlocks because the dialog's dismissal callback cannot run while the UI thread is blocked.
ViewModels can push/pop sub-pages via INavigationStack:
Push(viewModel)— Navigate to a child page.Pop()— Return to the previous page.- Used for detail views (e.g., password list → password detail → back to list).
User enters master password
│
▼
KDF (Argon2id or PBKDF2) + salt from VaultMetadata
│
▼
KEK (32 bytes) ── AES-GCM Decrypt ──► DEK (32 bytes)
│
Stored in PinnedSecureBuffer
│
AES-GCM Decrypt vault blob
│
▼
Vault (JSON → objects)
On save:
Vault objects → JSON → AES-GCM Encrypt with DEK → blob → VaultData table
On master password change:
New password → KDF → new KEK → AES-GCM Encrypt DEK → update VaultMetadata
(vault blob is NOT re-encrypted)
┌─────────────────┐ Native Messaging ┌──────────────────┐ Named Pipe ┌─────────────────┐
│ Browser │ (stdio: 4-byte len + │ PassKey │ (local IPC) │ PassKey │
│ Extension │ JSON payload) │ BrowserHost │ │ Desktop │
│ │◄───────────────────────►│ │◄────────────────►│ │
│ - popup.js │ │ - stdin/stdout │ │ - BrowserIpc │
│ - content.js │ │ - pipe client │ │ Service │
│ - background.js│ │ │ │ │
└─────────────────┘ └──────────────────┘ └─────────────────┘
- Extension generates ephemeral ECDH P-256 key pair.
- BrowserHost generates ephemeral ECDH P-256 key pair.
- Public keys exchanged → shared secret → HKDF-SHA256 → 32-byte session key.
- All subsequent messages encrypted with AES-256-GCM (unique nonce per message).
| Message | Direction | Description |
|---|---|---|
get-credentials |
Extension → Desktop | Get credentials matching a URL |
get-all-credentials |
Extension → Desktop | Get all vault credentials |
unlock-vault |
Extension → Desktop | Unlock vault with master password |
show-window |
Extension → Desktop | Bring Desktop app to foreground |