313 lines
15 KiB
Markdown
313 lines
15 KiB
Markdown
# PassKeeper 🔐
|
|
|
|
A self-hosted, zero-knowledge password manager — web app and Chrome/Firefox extension. Your master password and decrypted vault data **never leave your browser**.
|
|
|
|
---
|
|
|
|
## Features
|
|
|
|
### Web App
|
|
- **Zero-knowledge encryption** — AES-256-GCM client-side; 600k-iteration PBKDF2 vault key
|
|
- **7 item types** — Passwords, Secure Notes, Cards, Bank Accounts, Addresses, Identities, Passkeys
|
|
- **Vault item tags** — comma-separated tags stored inside the encrypted blob; sidebar tag filter; live badge preview in modal; no schema change required
|
|
- **Collapsible folder groups** — click any folder header in the vault list to collapse/expand; item count badge and chevron indicator; state persists across re-renders
|
|
- **TOTP / 2FA** — per-site TOTP codes stored in `plain.totp_uri`; live 6-digit display with countdown
|
|
- **Folder organisation** — create, rename, delete; filter vault by folder
|
|
- **Item sharing** — ECDH P-256 zero-knowledge re-encryption; item name encrypted with shared key — server never sees it
|
|
- **Emergency access** — configurable wait-timer access grant for a trusted contact
|
|
- **Security dashboard** — weak / reused / old / **no 2FA saved** / **HaveIBeenPwned breach check** (k-anonymity — passwords never transmitted)
|
|
- **Proactive health notifications** — sidebar badge and dismissible banner on vault load when breached, weak, or reused passwords are detected; HIBP runs in the background without blocking the UI; results cached so opening the Security tab is instant
|
|
- **Import / Export** — encrypted JSON backup; CSV export (plaintext, handle carefully); import from Chrome, Bitwarden, and 1Password CSV formats (RFC 4180 compliant parser)
|
|
- **Account MFA** — TOTP-based login (Google Authenticator / Authy); single-use code enforcement prevents replay attacks
|
|
- **Passkeys / WebAuthn** — register device biometrics or hardware keys (YubiKey, cross-device QR) as a sign-in method; master password still required to unlock vault (zero-knowledge preserved); manage passkeys in Account Settings; transport type shown per credential (📱 Device / 🔑 Security key)
|
|
- **Master password change** — atomic zero-knowledge re-encryption of entire vault including item names
|
|
- **Account recovery** — 128-bit recovery code; server never stores it; challenge-response proof prevents forgery
|
|
- **Audit log** — server-side trail of all create/edit/delete/import/export actions; no plaintext names ever logged
|
|
- **Encrypted item names** — `enc_name`/`iv_name`; server holds only the item type as a label
|
|
- **Browser history** — back/forward button works for all views (`history.pushState`)
|
|
- **Web-app auto-lock** — configurable inactivity timeout (5/10/15/30/60 min or Never); also locks immediately when tab is hidden (screen lock, minimize) for longer than the timeout
|
|
- **Clipboard auto-clear** — sensitive copies cleared after 30 seconds
|
|
- **Responsive layout** — phone, tablet, laptop, large desktop; collapsible sidebar
|
|
|
|
### Browser Extension (Chrome / Edge — Manifest V3 + Firefox — Manifest V2)
|
|
- **Autofill** — detects login forms; injects icon into username and password fields only (scored heuristic, not all inputs)
|
|
- **Smart domain matching** — matches by domain name; handles bare domains (`github.com`) and subdomains
|
|
- **Suggestion dropdown** — filters as you type; keyboard navigation (↑↓ Enter); one-click fill
|
|
- **Collapsible folder groups** — "All items" tab groups by folder with collapse/expand toggle matching the web app UX
|
|
- **Favorites tab** — items tagged `favorite` in the web app appear in a dedicated tab; marked with ★
|
|
- **Tag display** — purple badge pills on item rows; tags sourced from `plain.tags[]`
|
|
- **Three-dot flyout menu** — contextual actions: Open URL / Copy username / Copy password; built dynamically from what the item has
|
|
- **Dedicated copy-username button** — person icon alongside copy-password in every item row
|
|
- **Auto-save banner** — save/update credentials on form submit; duplicate detection; "Never for this site" blocklist
|
|
- **Pending-save badge** — red `!` on toolbar icon when a credential is waiting to be saved
|
|
- **Inline password generator** — fully CSPRNG (`crypto.getRandomValues` throughout, `Math.random` never called)
|
|
- **TOTP live display** — 6-digit code + countdown per item
|
|
- **SSO bridge** — log in once on the web app; extension picks up the session
|
|
- **Idle lock** — configurable auto-lock (1/5/10/30 min or Never)
|
|
- **Clipboard auto-clear** — passwords/usernames cleared from clipboard after 30 seconds
|
|
- **Keyboard shortcut** — `Ctrl+Shift+L` / `Cmd+Shift+L` to open popup (customisable at `chrome://extensions/shortcuts`)
|
|
- **Firefox compatible** — separate MV2 manifest + background script; same popup/content/bridge code
|
|
|
|
---
|
|
|
|
## Security Architecture
|
|
|
|
```
|
|
Master Password
|
|
│
|
|
├─ PBKDF2(email, 100k iter) ──► authHash ──► POST /api/auth/login
|
|
│ Argon2id(authHash) stored in DB
|
|
│ (transparently rehashed if params upgraded)
|
|
│
|
|
└─ PBKDF2(enc_key_salt, 600k iter) ──► vaultKey (browser memory only)
|
|
│
|
|
AES-256-GCM encrypt
|
|
│
|
|
enc_data + iv (item payload + tags)
|
|
enc_name + iv_name (item name)
|
|
│
|
|
POST /api/vault ──► Server stores ciphertext only
|
|
|
|
Sharing: ECDH(Alice_priv, Bob_pub) ──► sharedKey ──► AES-256-GCM(enc_data + enc_name)
|
|
Server stores only ciphertext — cannot read item content or item name
|
|
```
|
|
|
|
### Security hardening highlights
|
|
- **TOTP replay prevention** — each 6-digit code is single-use (120s window); recorded in `totp_used_codes` table
|
|
- **Recovery proof** — `enc_key_salt` not returned by server during recovery; client must decrypt the recovery blob to prove code possession
|
|
- **folder_id ownership** — all create/update/import operations validate folder belongs to current user
|
|
- **Audit log privacy** — item names and shared item names never appear in server-side audit logs
|
|
- **Extension fingerprinting** — `web_accessible_resources: []` blocks external pages from probing extension files
|
|
- **Passkey / WebAuthn** — FIDO2 assertion proves identity to the server without a password; vault key still derived from master password client-side; `sign_count` updated on each use for clone detection
|
|
- **Password age tracking** — `password_changed_at` stored inside encrypted blob; security dashboard uses actual password change date, not item creation date
|
|
- **Emergency access stale snapshot detection** — server flags provisioned snapshots created before the `enc_name` fix; UI prompts grantor to re-provision
|
|
|
|
A database breach exposes only encrypted ciphertext. The server cannot read vault names, passwords, tags, or shared item names.
|
|
|
|
---
|
|
|
|
## Tech Stack
|
|
|
|
| Layer | Technology |
|
|
|---|---|
|
|
| Backend | Python 3.12, Flask 3.x |
|
|
| Database | MySQL 8.x |
|
|
| Frontend | Vanilla JS, Web Crypto API, Jinja2 |
|
|
| Auth | Argon2id + PBKDF2 + JWT (HS256) + WebAuthn (FIDO2) |
|
|
| Encryption | AES-256-GCM (client-side) |
|
|
| Extension | Chrome MV3 / Firefox MV2 |
|
|
| Web server | Nginx + Gunicorn + systemd |
|
|
| Rate limiting | Flask-Limiter + Redis |
|
|
| TLS | Let's Encrypt / Certbot |
|
|
|
|
---
|
|
|
|
## Getting Started
|
|
|
|
### Prerequisites
|
|
- Python 3.12+
|
|
- MySQL 8.x
|
|
- No Node.js or build step required
|
|
|
|
### Development (Windows)
|
|
|
|
```bash
|
|
git clone https://github.com/yourname/passkeeper
|
|
cd passkeeper
|
|
python -m venv .venv
|
|
.venv\Scripts\activate
|
|
pip install -r requirements.txt
|
|
copy .env.example .env
|
|
```
|
|
|
|
Edit `.env`:
|
|
```env
|
|
FLASK_ENV=development
|
|
SECRET_KEY=<64-char hex>
|
|
JWT_SECRET_KEY=<64-char hex>
|
|
MYSQL_HOST=localhost
|
|
MYSQL_USER=passkeeper
|
|
MYSQL_PASSWORD=yourpassword
|
|
MYSQL_DB=passkeeper
|
|
TOTP_ENCRYPTION_KEY=<64-char hex>
|
|
```
|
|
|
|
```bash
|
|
mysql -u root -p -e "CREATE DATABASE passkeeper CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
|
|
mysql -u root -p -e "CREATE USER 'passkeeper'@'localhost' IDENTIFIED BY 'yourpassword'; GRANT ALL ON passkeeper.* TO 'passkeeper'@'localhost'; FLUSH PRIVILEGES;"
|
|
python reset_db.py
|
|
python run.py
|
|
```
|
|
|
|
Generate secrets:
|
|
```bash
|
|
python -c "import secrets; print(secrets.token_hex(32))"
|
|
```
|
|
|
|
### Load the Extension (Chrome)
|
|
1. `chrome://extensions/` → Enable **Developer mode**
|
|
2. **Load unpacked** → select `extension/` folder
|
|
3. Reload after any JS/CSS changes
|
|
|
|
### Load the Extension (Firefox)
|
|
1. `about:debugging` → **This Firefox** → **Load Temporary Add-on**
|
|
2. Select `extension/manifest.firefox.json`
|
|
|
|
---
|
|
|
|
## Production Deployment
|
|
|
|
### 1. Server dependencies
|
|
```bash
|
|
sudo apt update && sudo apt install python3.12 python3.12-venv mysql-server nginx \
|
|
certbot python3-certbot-nginx redis-server
|
|
```
|
|
|
|
### 2. Application setup
|
|
```bash
|
|
cd /var/www/passkeeper
|
|
python3.12 -m venv .venv && source .venv/bin/activate
|
|
pip install -r requirements.txt
|
|
cp .env.example .env # set production values
|
|
flask db upgrade # run all migrations
|
|
```
|
|
|
|
### 3. systemd service
|
|
```bash
|
|
sudo cp scripts/passkeeper.service /etc/systemd/system/
|
|
sudo systemctl daemon-reload && sudo systemctl enable --now passkeeper
|
|
```
|
|
|
|
### 4. Nginx
|
|
```bash
|
|
sudo cp scripts/passkeeper-nginx.conf /etc/nginx/sites-available/passkeeper
|
|
sudo ln -s /etc/nginx/sites-available/passkeeper /etc/nginx/sites-enabled/
|
|
sudo certbot --nginx -d yourdomain.com
|
|
sudo nginx -t && sudo systemctl reload nginx
|
|
```
|
|
|
|
### 5. Future schema migrations
|
|
```bash
|
|
flask db upgrade && sudo systemctl reload passkeeper
|
|
```
|
|
|
|
---
|
|
|
|
## Environment Variables
|
|
|
|
| Variable | Description |
|
|
|---|---|
|
|
| `SECRET_KEY` | Flask session secret (64-char hex) |
|
|
| `JWT_SECRET_KEY` | JWT signing secret (64-char hex) |
|
|
| `MYSQL_HOST` / `MYSQL_USER` / `MYSQL_PASSWORD` / `MYSQL_DB` | Database |
|
|
| `TOTP_ENCRYPTION_KEY` | Server-side AES key for TOTP secrets (64-char hex) |
|
|
| `RATELIMIT_STORAGE_URI` | Redis URI — required in production (`redis://127.0.0.1:6379/0`) |
|
|
| `CORS_ORIGINS` | Allowed origins (`*` in dev, domain in prod) |
|
|
| `WEBAUTHN_RP_ID` | Passkey relying party ID — effective domain, no scheme (`pwkeeper.ngodanguyen.tech`) |
|
|
| `WEBAUTHN_RP_NAME` | Passkey relying party display name (`PassKeeper`) |
|
|
| `WEBAUTHN_ORIGINS` | Comma-separated allowed origins for WebAuthn (`https://pwkeeper.ngodanguyen.tech`) |
|
|
|
|
---
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
passkeeper/
|
|
├── app/ # Flask application
|
|
│ ├── models/ # SQLAlchemy models
|
|
│ ├── routes/ # API blueprints (auth, vault, folders, sharing, emergency)
|
|
│ ├── services/ # Auth (Argon2id, JWT, TOTP encryption, TOTP replay helpers)
|
|
│ ├── static/js/ # Client-side crypto + vault UI
|
|
│ └── templates/ # Jinja2 templates
|
|
├── extension/ # Browser extension
|
|
│ ├── popup/ # Popup UI
|
|
│ ├── content/ # Content script (autofill, field detection)
|
|
│ ├── shared/ # Shared crypto + Firefox polyfill
|
|
│ ├── bridge/ # SSO bridge
|
|
│ ├── background.js # Chrome MV3 service worker
|
|
│ ├── background.firefox.js # Firefox MV2 background page
|
|
│ ├── manifest.json # Chrome/Edge MV3
|
|
│ └── manifest.firefox.json # Firefox MV2
|
|
├── migrations/ # Alembic migration scripts
|
|
├── scripts/ # Nginx, systemd, backup
|
|
└── requirements.txt
|
|
```
|
|
|
|
---
|
|
|
|
## API Overview
|
|
|
|
All vault/folder endpoints require `Authorization: Bearer <access_token>`.
|
|
|
|
| Method | Endpoint | Description |
|
|
|---|---|---|
|
|
| POST | `/api/auth/register` | Create account |
|
|
| POST | `/api/auth/login` | Authenticate |
|
|
| POST | `/api/auth/mfa/verify` | Complete MFA (single-use code) |
|
|
| POST | `/api/auth/refresh` | Rotate tokens |
|
|
| POST | `/api/auth/logout` | Blacklist tokens |
|
|
| GET | `/api/vault` | List encrypted items |
|
|
| POST | `/api/vault` | Create item (`enc_data`, `iv`, `enc_name`, `iv_name`, tags in payload) |
|
|
| PUT | `/api/vault/<id>` | Update item |
|
|
| DELETE | `/api/vault/<id>` | Delete item |
|
|
| GET | `/api/vault/export` | Download encrypted JSON backup |
|
|
| POST | `/api/vault/import` | Bulk import; returns `{ imported, skipped }` |
|
|
| GET/POST | `/api/folders` | List / create folders |
|
|
| POST | `/api/sharing` | Share item — sends `enc_name`/`iv_name` for ZK name |
|
|
| POST | `/api/emergency` | Create emergency access grant |
|
|
| POST | `/api/auth/change-password` | Atomic vault re-encryption |
|
|
| POST | `/api/auth/recover` | Account recovery (one-time) |
|
|
| POST | `/api/webauthn/register/begin` | Start passkey registration |
|
|
| POST | `/api/webauthn/register/complete` | Finish passkey registration |
|
|
| POST | `/api/webauthn/authenticate/begin` | Start passkey login |
|
|
| POST | `/api/webauthn/authenticate/complete` | Complete passkey login → tokens |
|
|
| GET | `/api/webauthn/credentials` | List registered passkeys |
|
|
| DELETE | `/api/webauthn/credentials/<id>` | Remove a passkey |
|
|
|
|
---
|
|
|
|
## Item Tagging
|
|
|
|
Tags are stored as `plain.tags: string[]` inside the encrypted vault blob — the server never sees them and no schema change is required.
|
|
|
|
**Web app:** Add tags in the item edit modal (comma-separated). Tags appear as purple badge pills on item rows. A Tags section in the sidebar lets you filter by any tag. Search respects the active tag filter.
|
|
|
|
**Extension:** Tags appear as `.pk-tag` badges on item rows. Items tagged `favorite` appear in the **Favorites** tab and show a ★ in the site label.
|
|
|
|
---
|
|
|
|
## Backup
|
|
|
|
```bash
|
|
# Install cron job
|
|
sudo cp scripts/passkeeper-logrotate /etc/logrotate.d/passkeeper
|
|
crontab scripts/backup.cron
|
|
|
|
# Manual backup
|
|
bash scripts/backup_db.sh
|
|
```
|
|
|
|
---
|
|
|
|
## Licence
|
|
|
|
MIT — see `LICENSE`.
|
|
|
|
---
|
|
|
|
## Roadmap
|
|
|
|
| # | Feature | Notes |
|
|
|---|---------|-------|
|
|
| 1 | **One-time share links** | Single-use encrypted link for non-account recipients; key in URL fragment |
|
|
| 2 | **Custom fields** | Arbitrary key-value pairs per item; stored in encrypted blob |
|
|
| 3 | **Trusted devices** | Skip MFA for N days on trusted browsers |
|
|
| 4 | **Email notifications** | Alert on login from new IP, MFA changes, password change |
|
|
| 5 | **Audit log improvements** | Filter by type/date, CSV export, pagination |
|
|
| 6 | **Vault item attachments** | Client-side encrypted file attachments per item |
|
|
| 7 | **Collections / nested folders** | Group folders into top-level collections |
|
|
| 8 | **iOS AutoFill Extension** | Native credential filling in Safari and iOS apps |
|
|
| 9 | **Extension TOTP QR capture** | Detect and save TOTP secrets during 2FA setup |
|
|
| 10 | **Admin panel** | User management for multi-user deployments |
|
|
| 11 | **Backup verification cron** | Weekly automated restore test |
|
|
| 12 | **Health check endpoint** | `GET /health` for uptime monitors | |