zenithpasskeys 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- zenithpasskeys-1.1.0/.gitignore +80 -0
- zenithpasskeys-1.1.0/PKG-INFO +204 -0
- zenithpasskeys-1.1.0/README.md +169 -0
- zenithpasskeys-1.1.0/pyproject.toml +55 -0
- zenithpasskeys-1.1.0/src/zenith_passkeys/__init__.py +1 -0
- zenithpasskeys-1.1.0/src/zenith_passkeys/__main__.py +4 -0
- zenithpasskeys-1.1.0/src/zenith_passkeys/backup/__init__.py +4 -0
- zenithpasskeys-1.1.0/src/zenith_passkeys/commands/__init__.py +0 -0
- zenithpasskeys-1.1.0/src/zenith_passkeys/commands/_shared.py +43 -0
- zenithpasskeys-1.1.0/src/zenith_passkeys/commands/audit_cmd.py +93 -0
- zenithpasskeys-1.1.0/src/zenith_passkeys/commands/backup_cmd.py +163 -0
- zenithpasskeys-1.1.0/src/zenith_passkeys/commands/change_password_cmd.py +34 -0
- zenithpasskeys-1.1.0/src/zenith_passkeys/commands/config_cmd.py +35 -0
- zenithpasskeys-1.1.0/src/zenith_passkeys/commands/delete_cmd.py +60 -0
- zenithpasskeys-1.1.0/src/zenith_passkeys/commands/export_cmd.py +79 -0
- zenithpasskeys-1.1.0/src/zenith_passkeys/commands/import_cmd.py +166 -0
- zenithpasskeys-1.1.0/src/zenith_passkeys/commands/init_cmd.py +76 -0
- zenithpasskeys-1.1.0/src/zenith_passkeys/commands/list_cmd.py +74 -0
- zenithpasskeys-1.1.0/src/zenith_passkeys/commands/lock_cmd.py +26 -0
- zenithpasskeys-1.1.0/src/zenith_passkeys/commands/logs_cmd.py +47 -0
- zenithpasskeys-1.1.0/src/zenith_passkeys/commands/recover_cmd.py +48 -0
- zenithpasskeys-1.1.0/src/zenith_passkeys/core/__init__.py +0 -0
- zenithpasskeys-1.1.0/src/zenith_passkeys/core/credentials.py +43 -0
- zenithpasskeys-1.1.0/src/zenith_passkeys/core/crypto.py +119 -0
- zenithpasskeys-1.1.0/src/zenith_passkeys/core/errors.py +38 -0
- zenithpasskeys-1.1.0/src/zenith_passkeys/core/paths.py +32 -0
- zenithpasskeys-1.1.0/src/zenith_passkeys/core/psl.py +192 -0
- zenithpasskeys-1.1.0/src/zenith_passkeys/core/recovery.py +26 -0
- zenithpasskeys-1.1.0/src/zenith_passkeys/core/schema.py +57 -0
- zenithpasskeys-1.1.0/src/zenith_passkeys/core/vault.py +247 -0
- zenithpasskeys-1.1.0/src/zenith_passkeys/core/webauthn.py +129 -0
- zenithpasskeys-1.1.0/src/zenith_passkeys/main.py +76 -0
- zenithpasskeys-1.1.0/src/zenith_passkeys/native_host/__init__.py +0 -0
- zenithpasskeys-1.1.0/src/zenith_passkeys/native_host/host.py +636 -0
- zenithpasskeys-1.1.0/src/zenith_passkeys/native_host/host_wrapper.py +40 -0
- zenithpasskeys-1.1.0/src/zenith_passkeys/native_host/lock_sentinel.py +68 -0
- zenithpasskeys-1.1.0/src/zenith_passkeys/native_host/manifest.py +168 -0
- zenithpasskeys-1.1.0/src/zenith_passkeys/native_host/protocol.py +32 -0
- zenithpasskeys-1.1.0/tests/__init__.py +0 -0
- zenithpasskeys-1.1.0/tests/audit_test.py +738 -0
- zenithpasskeys-1.1.0/tests/test_backup.py +106 -0
- zenithpasskeys-1.1.0/tests/test_cli_standalone.py +53 -0
- zenithpasskeys-1.1.0/tests/test_commands.py +37 -0
- zenithpasskeys-1.1.0/tests/test_crypto.py +89 -0
- zenithpasskeys-1.1.0/tests/test_host_updates.py +114 -0
- zenithpasskeys-1.1.0/tests/test_native_host.py +51 -0
- zenithpasskeys-1.1.0/tests/test_native_host_extended.py +102 -0
- zenithpasskeys-1.1.0/tests/test_psl.py +117 -0
- zenithpasskeys-1.1.0/tests/test_vault.py +101 -0
- zenithpasskeys-1.1.0/tests/test_webauthn.py +57 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
develop-eggs/
|
|
9
|
+
dist/
|
|
10
|
+
downloads/
|
|
11
|
+
eggs/
|
|
12
|
+
.eggs/
|
|
13
|
+
lib/
|
|
14
|
+
lib64/
|
|
15
|
+
parts/
|
|
16
|
+
sdist/
|
|
17
|
+
var/
|
|
18
|
+
wheels/
|
|
19
|
+
share/python-wheels/
|
|
20
|
+
*.egg-info/
|
|
21
|
+
.installed.cfg
|
|
22
|
+
*.egg
|
|
23
|
+
MANIFEST
|
|
24
|
+
|
|
25
|
+
# Virtual Environments
|
|
26
|
+
venv/
|
|
27
|
+
.venv/
|
|
28
|
+
env/
|
|
29
|
+
.env/
|
|
30
|
+
env.bak/
|
|
31
|
+
venv.bak/
|
|
32
|
+
|
|
33
|
+
# Environment Variables
|
|
34
|
+
.env
|
|
35
|
+
.env.local
|
|
36
|
+
|
|
37
|
+
# Unit test / coverage reports
|
|
38
|
+
htmlcov/
|
|
39
|
+
.tox/
|
|
40
|
+
.nox/
|
|
41
|
+
.coverage
|
|
42
|
+
.coverage.*
|
|
43
|
+
.cache
|
|
44
|
+
nosetests.xml
|
|
45
|
+
coverage.xml
|
|
46
|
+
*.cover
|
|
47
|
+
*.py,cover
|
|
48
|
+
.hypothesis/
|
|
49
|
+
.pytest_cache/
|
|
50
|
+
cover/
|
|
51
|
+
|
|
52
|
+
# IDE / Editor
|
|
53
|
+
.vscode/
|
|
54
|
+
.idea/
|
|
55
|
+
*.swp
|
|
56
|
+
*.swo
|
|
57
|
+
.DS_Store
|
|
58
|
+
Thumbs.db
|
|
59
|
+
*.bak
|
|
60
|
+
*.tmp
|
|
61
|
+
*~
|
|
62
|
+
|
|
63
|
+
# Project specific
|
|
64
|
+
/plan.md
|
|
65
|
+
/audit_test.py
|
|
66
|
+
/cli/tests/audit_test.py
|
|
67
|
+
|
|
68
|
+
# Sensitive files — NEVER commit these
|
|
69
|
+
vault.enc
|
|
70
|
+
*.enc
|
|
71
|
+
gdrive_token.json
|
|
72
|
+
host_debug.log
|
|
73
|
+
|
|
74
|
+
# Debug/temp scripts
|
|
75
|
+
cli/fix_tests.py
|
|
76
|
+
cli/test_debug.py
|
|
77
|
+
cli/test_host.py
|
|
78
|
+
|
|
79
|
+
# Native host manifests (generated per-machine)
|
|
80
|
+
native-manifests/
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: zenithpasskeys
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: Local-first, air-gapped passkey manager with encrypted vault
|
|
5
|
+
Project-URL: Homepage, https://github.com/roshhellwett/projectwarden
|
|
6
|
+
Project-URL: Repository, https://github.com/roshhellwett/projectwarden
|
|
7
|
+
Project-URL: Documentation, https://github.com/roshhellwett/projectwarden/blob/main/docs/SETUP.md
|
|
8
|
+
Project-URL: Issues, https://github.com/roshhellwett/projectwarden/issues
|
|
9
|
+
Author-email: Zenith Open Source Projects <zenithprojects@icloud.com>
|
|
10
|
+
License: MIT
|
|
11
|
+
Keywords: cryptography,fido2,passkeys,password-manager,security,webauthn
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Security
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Requires-Dist: cbor2>=5.6
|
|
25
|
+
Requires-Dist: click>=8.1
|
|
26
|
+
Requires-Dist: cryptography>=43.0
|
|
27
|
+
Requires-Dist: filelock>=3.13
|
|
28
|
+
Requires-Dist: platformdirs>=4.0
|
|
29
|
+
Requires-Dist: rich>=13.0
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# Zenith Passkeys (`zenithpasskeys`)
|
|
37
|
+
|
|
38
|
+
[](https://pypi.org/project/zenithpasskeys/)
|
|
39
|
+
[](https://pypi.org/project/zenithpasskeys/)
|
|
40
|
+
[](https://opensource.org/licenses/MIT)
|
|
41
|
+
|
|
42
|
+
> **Local-first, air-gapped passkey (WebAuthn & FIDO2) manager with an encrypted vault.**
|
|
43
|
+
> Built and managed by **Zenith Open Source Projects**.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Highlights
|
|
48
|
+
|
|
49
|
+
- 🔒 **100% Local-First & Air-Gapped:** Zero cloud accounts, zero telemetry, zero external network requests. Your private keys never touch the internet.
|
|
50
|
+
- 🛡️ **Hardware-Grade Cryptography:** Master Encryption Key (MEK) protected by **Argon2id** (64MB RAM, 3 iterations, 4 lanes) and **AES-256-GCM** authenticated encryption.
|
|
51
|
+
- 🔑 **Dual-Slot Emergency Recovery:** Instant vault recovery using a 32-character Crockford Base32 emergency key (`XXXX-XXXX-...`). Re-wraps the MEK under a new password without losing any passkeys.
|
|
52
|
+
- ⚡ **Zero Setup Required:** Runs out of the box in any environment without manual virtualenv activation or system compilation.
|
|
53
|
+
- 🌐 **Dual Standalone or Browser Mode:** Use purely from the terminal as an independent passkey vault, or pair seamlessly with the Zenith Passkeys Chrome/Edge/Firefox Web Extension.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
Install in any environment via `pip`:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install zenithpasskeys
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Or install cleanly using `pipx` (recommended for isolated CLI tools with zero venv management):
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pipx install zenithpasskeys
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Once installed, you can launch the CLI using either command:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
zenith-passkeys
|
|
75
|
+
# or
|
|
76
|
+
zenithpasskeys
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Quickstart Guide
|
|
82
|
+
|
|
83
|
+
### 1. Initialize Your Secure Vault
|
|
84
|
+
Create a new encrypted vault and generate your 32-character Emergency Recovery Key:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
zenith-passkeys init
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Save the printed recovery key in a physical notebook or cold storage.
|
|
91
|
+
|
|
92
|
+
### 2. View Stored Passkeys
|
|
93
|
+
List all passkey credentials stored in your local vault:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
zenith-passkeys list
|
|
97
|
+
|
|
98
|
+
# Filter by domain:
|
|
99
|
+
zenith-passkeys list --domain github.com
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 3. Run a Security Audit
|
|
103
|
+
Perform an offline diagnostic check for duplicate credentials or stale passkeys:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
zenith-passkeys audit
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 4. Export & Import Credentials
|
|
110
|
+
Securely export your credentials (encrypted with a dedicated export password):
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# Encrypted export (recommended):
|
|
114
|
+
zenith-passkeys export --encrypt
|
|
115
|
+
|
|
116
|
+
# Plaintext export:
|
|
117
|
+
zenith-passkeys export
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Import credentials from another Zenith instance or migration file:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
zenith-passkeys import export.json
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### 5. Create Local Backup Snapshots
|
|
127
|
+
Create an encrypted backup snapshot directly to an external drive, USB stick, or backup directory:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
zenith-passkeys backup create /path/to/backup/zenith-vault-backup.enc
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### 6. Emergency Recovery
|
|
134
|
+
Forgot your master password? Recover your vault and set a new password using your Emergency Recovery Key:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
zenith-passkeys recover
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### 7. Instant Lock
|
|
141
|
+
Clear active authentication sessions and lock the vault across all processes:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
zenith-passkeys lock
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Command Reference
|
|
150
|
+
|
|
151
|
+
| Command | Description |
|
|
152
|
+
|---|---|
|
|
153
|
+
| `zenith-passkeys init` | Initialize a new vault and register native host messaging |
|
|
154
|
+
| `zenith-passkeys list` | Display stored passkeys with rich metadata and domain filtering |
|
|
155
|
+
| `zenith-passkeys audit` | Run offline security diagnostics and health scoring |
|
|
156
|
+
| `zenith-passkeys export` | Export credentials to encrypted or plaintext JSON |
|
|
157
|
+
| `zenith-passkeys import` | Import credentials from encrypted or plaintext export files |
|
|
158
|
+
| `zenith-passkeys backup` | Create or restore encrypted vault backup snapshots |
|
|
159
|
+
| `zenith-passkeys recover` | Reset master password using 32-character Emergency Recovery Key |
|
|
160
|
+
| `zenith-passkeys change-password` | Update master password and re-wrap vault master key |
|
|
161
|
+
| `zenith-passkeys lock` | Clear active memory session and lock the vault |
|
|
162
|
+
| `zenith-passkeys config` | Configure auto-lock timeout (inactivity minutes) |
|
|
163
|
+
| `zenith-passkeys delete` | Permanently delete a credential by domain |
|
|
164
|
+
| `zenith-passkeys logs` | View internal diagnostic action audit trail |
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Pairing with the Browser Extension (Optional)
|
|
169
|
+
|
|
170
|
+
Zenith Passkeys works as a standalone terminal manager, but also pairs with the **Zenith Passkeys Browser Extension** for passwordless web logins.
|
|
171
|
+
|
|
172
|
+
When you run:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
zenith-passkeys init
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
it automatically writes the Native Messaging manifest for Google Chrome, Microsoft Edge, and Mozilla Firefox. The browser extension can then securely communicate with the local `zenith-passkeys-host` daemon to sign WebAuthn ceremonies on sites like GitHub, Google, and Amazon.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Security Architecture
|
|
183
|
+
|
|
184
|
+
1. **Argon2id Key Derivation:**
|
|
185
|
+
- Memory: 64 MB (`m=65536`)
|
|
186
|
+
- Iterations: 3 (`t=3`)
|
|
187
|
+
- Parallelism: 4 lanes (`p=4`)
|
|
188
|
+
2. **Dual-Slot Key Wrapping:**
|
|
189
|
+
- Slot 0 (`pwWrapped`): Master Encryption Key encrypted under master password.
|
|
190
|
+
- Slot 1 (`recWrapped`): Master Encryption Key encrypted under the 32-character recovery key.
|
|
191
|
+
- Password changes re-wrap Slot 0 while keeping the recovery key permanently functional.
|
|
192
|
+
3. **Atomic Operations & Integrity:**
|
|
193
|
+
- Atomic file replace using tempfile swaps to eliminate corruption risks.
|
|
194
|
+
- File locks via `filelock` prevent concurrent write race conditions.
|
|
195
|
+
- Public Suffix List (PSL) validation prevents cross-subdomain credential spoofing.
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## License & Support
|
|
200
|
+
|
|
201
|
+
- **License:** Open source under the [MIT License](https://opensource.org/licenses/MIT).
|
|
202
|
+
- **Repository:** [GitHub](https://github.com/roshhellwett/projectwarden)
|
|
203
|
+
- **Support & Security Disclosures:** `zenithprojects@icloud.com`
|
|
204
|
+
- **Maintained by:** **Zenith Open Source Projects**
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Zenith Passkeys (`zenithpasskeys`)
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/zenithpasskeys/)
|
|
4
|
+
[](https://pypi.org/project/zenithpasskeys/)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
7
|
+
> **Local-first, air-gapped passkey (WebAuthn & FIDO2) manager with an encrypted vault.**
|
|
8
|
+
> Built and managed by **Zenith Open Source Projects**.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Highlights
|
|
13
|
+
|
|
14
|
+
- 🔒 **100% Local-First & Air-Gapped:** Zero cloud accounts, zero telemetry, zero external network requests. Your private keys never touch the internet.
|
|
15
|
+
- 🛡️ **Hardware-Grade Cryptography:** Master Encryption Key (MEK) protected by **Argon2id** (64MB RAM, 3 iterations, 4 lanes) and **AES-256-GCM** authenticated encryption.
|
|
16
|
+
- 🔑 **Dual-Slot Emergency Recovery:** Instant vault recovery using a 32-character Crockford Base32 emergency key (`XXXX-XXXX-...`). Re-wraps the MEK under a new password without losing any passkeys.
|
|
17
|
+
- ⚡ **Zero Setup Required:** Runs out of the box in any environment without manual virtualenv activation or system compilation.
|
|
18
|
+
- 🌐 **Dual Standalone or Browser Mode:** Use purely from the terminal as an independent passkey vault, or pair seamlessly with the Zenith Passkeys Chrome/Edge/Firefox Web Extension.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
Install in any environment via `pip`:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install zenithpasskeys
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Or install cleanly using `pipx` (recommended for isolated CLI tools with zero venv management):
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pipx install zenithpasskeys
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Once installed, you can launch the CLI using either command:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
zenith-passkeys
|
|
40
|
+
# or
|
|
41
|
+
zenithpasskeys
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Quickstart Guide
|
|
47
|
+
|
|
48
|
+
### 1. Initialize Your Secure Vault
|
|
49
|
+
Create a new encrypted vault and generate your 32-character Emergency Recovery Key:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
zenith-passkeys init
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Save the printed recovery key in a physical notebook or cold storage.
|
|
56
|
+
|
|
57
|
+
### 2. View Stored Passkeys
|
|
58
|
+
List all passkey credentials stored in your local vault:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
zenith-passkeys list
|
|
62
|
+
|
|
63
|
+
# Filter by domain:
|
|
64
|
+
zenith-passkeys list --domain github.com
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 3. Run a Security Audit
|
|
68
|
+
Perform an offline diagnostic check for duplicate credentials or stale passkeys:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
zenith-passkeys audit
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 4. Export & Import Credentials
|
|
75
|
+
Securely export your credentials (encrypted with a dedicated export password):
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Encrypted export (recommended):
|
|
79
|
+
zenith-passkeys export --encrypt
|
|
80
|
+
|
|
81
|
+
# Plaintext export:
|
|
82
|
+
zenith-passkeys export
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Import credentials from another Zenith instance or migration file:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
zenith-passkeys import export.json
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 5. Create Local Backup Snapshots
|
|
92
|
+
Create an encrypted backup snapshot directly to an external drive, USB stick, or backup directory:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
zenith-passkeys backup create /path/to/backup/zenith-vault-backup.enc
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 6. Emergency Recovery
|
|
99
|
+
Forgot your master password? Recover your vault and set a new password using your Emergency Recovery Key:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
zenith-passkeys recover
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 7. Instant Lock
|
|
106
|
+
Clear active authentication sessions and lock the vault across all processes:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
zenith-passkeys lock
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Command Reference
|
|
115
|
+
|
|
116
|
+
| Command | Description |
|
|
117
|
+
|---|---|
|
|
118
|
+
| `zenith-passkeys init` | Initialize a new vault and register native host messaging |
|
|
119
|
+
| `zenith-passkeys list` | Display stored passkeys with rich metadata and domain filtering |
|
|
120
|
+
| `zenith-passkeys audit` | Run offline security diagnostics and health scoring |
|
|
121
|
+
| `zenith-passkeys export` | Export credentials to encrypted or plaintext JSON |
|
|
122
|
+
| `zenith-passkeys import` | Import credentials from encrypted or plaintext export files |
|
|
123
|
+
| `zenith-passkeys backup` | Create or restore encrypted vault backup snapshots |
|
|
124
|
+
| `zenith-passkeys recover` | Reset master password using 32-character Emergency Recovery Key |
|
|
125
|
+
| `zenith-passkeys change-password` | Update master password and re-wrap vault master key |
|
|
126
|
+
| `zenith-passkeys lock` | Clear active memory session and lock the vault |
|
|
127
|
+
| `zenith-passkeys config` | Configure auto-lock timeout (inactivity minutes) |
|
|
128
|
+
| `zenith-passkeys delete` | Permanently delete a credential by domain |
|
|
129
|
+
| `zenith-passkeys logs` | View internal diagnostic action audit trail |
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Pairing with the Browser Extension (Optional)
|
|
134
|
+
|
|
135
|
+
Zenith Passkeys works as a standalone terminal manager, but also pairs with the **Zenith Passkeys Browser Extension** for passwordless web logins.
|
|
136
|
+
|
|
137
|
+
When you run:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
zenith-passkeys init
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
it automatically writes the Native Messaging manifest for Google Chrome, Microsoft Edge, and Mozilla Firefox. The browser extension can then securely communicate with the local `zenith-passkeys-host` daemon to sign WebAuthn ceremonies on sites like GitHub, Google, and Amazon.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Security Architecture
|
|
148
|
+
|
|
149
|
+
1. **Argon2id Key Derivation:**
|
|
150
|
+
- Memory: 64 MB (`m=65536`)
|
|
151
|
+
- Iterations: 3 (`t=3`)
|
|
152
|
+
- Parallelism: 4 lanes (`p=4`)
|
|
153
|
+
2. **Dual-Slot Key Wrapping:**
|
|
154
|
+
- Slot 0 (`pwWrapped`): Master Encryption Key encrypted under master password.
|
|
155
|
+
- Slot 1 (`recWrapped`): Master Encryption Key encrypted under the 32-character recovery key.
|
|
156
|
+
- Password changes re-wrap Slot 0 while keeping the recovery key permanently functional.
|
|
157
|
+
3. **Atomic Operations & Integrity:**
|
|
158
|
+
- Atomic file replace using tempfile swaps to eliminate corruption risks.
|
|
159
|
+
- File locks via `filelock` prevent concurrent write race conditions.
|
|
160
|
+
- Public Suffix List (PSL) validation prevents cross-subdomain credential spoofing.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## License & Support
|
|
165
|
+
|
|
166
|
+
- **License:** Open source under the [MIT License](https://opensource.org/licenses/MIT).
|
|
167
|
+
- **Repository:** [GitHub](https://github.com/roshhellwett/projectwarden)
|
|
168
|
+
- **Support & Security Disclosures:** `zenithprojects@icloud.com`
|
|
169
|
+
- **Maintained by:** **Zenith Open Source Projects**
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling<1.27"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "zenithpasskeys"
|
|
7
|
+
version = "1.1.0"
|
|
8
|
+
description = "Local-first, air-gapped passkey manager with encrypted vault"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [{name = "Zenith Open Source Projects", email = "zenithprojects@icloud.com"}]
|
|
13
|
+
keywords = ["passkeys", "webauthn", "fido2", "password-manager", "cryptography", "security"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.10",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Programming Language :: Python :: 3.14",
|
|
23
|
+
"Topic :: Security",
|
|
24
|
+
"Environment :: Console",
|
|
25
|
+
"Operating System :: OS Independent",
|
|
26
|
+
]
|
|
27
|
+
dependencies = [
|
|
28
|
+
"click>=8.1",
|
|
29
|
+
"cryptography>=43.0",
|
|
30
|
+
"platformdirs>=4.0",
|
|
31
|
+
"filelock>=3.13",
|
|
32
|
+
"cbor2>=5.6",
|
|
33
|
+
"rich>=13.0",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.optional-dependencies]
|
|
37
|
+
dev = [
|
|
38
|
+
"pytest>=8.0",
|
|
39
|
+
"pytest-cov>=5.0",
|
|
40
|
+
"ruff>=0.5",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[project.scripts]
|
|
44
|
+
zenith-passkeys = "zenith_passkeys.main:cli"
|
|
45
|
+
zenithpasskeys = "zenith_passkeys.main:cli"
|
|
46
|
+
zenith-passkeys-host = "zenith_passkeys.native_host.host:main"
|
|
47
|
+
|
|
48
|
+
[project.urls]
|
|
49
|
+
Homepage = "https://github.com/roshhellwett/projectwarden"
|
|
50
|
+
Repository = "https://github.com/roshhellwett/projectwarden"
|
|
51
|
+
Documentation = "https://github.com/roshhellwett/projectwarden/blob/main/docs/SETUP.md"
|
|
52
|
+
Issues = "https://github.com/roshhellwett/projectwarden/issues"
|
|
53
|
+
|
|
54
|
+
[tool.hatch.build.targets.wheel]
|
|
55
|
+
packages = ["src/zenith_passkeys"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.0.0"
|
|
File without changes
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from rich.console import Console
|
|
3
|
+
from rich.markup import escape
|
|
4
|
+
|
|
5
|
+
from ..core.errors import SUPPORT_EMAIL
|
|
6
|
+
from ..core.vault import VaultManager
|
|
7
|
+
|
|
8
|
+
console = Console()
|
|
9
|
+
|
|
10
|
+
VAULT_MISSING_MSG = "No vault found. Run [green]'zenith-passkeys init'[/green] first."
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def handle_vault_error(e: Exception) -> None:
|
|
14
|
+
"""Translate vault-layer failures into clean, actionable CLI errors."""
|
|
15
|
+
from cryptography.exceptions import InvalidTag
|
|
16
|
+
|
|
17
|
+
if isinstance(e, InvalidTag) or "password" in str(e).lower():
|
|
18
|
+
console.print("[red]Wrong password.[/red]")
|
|
19
|
+
elif "recovery" in str(e).lower():
|
|
20
|
+
console.print("[red]Wrong recovery key or corrupted vault.[/red]")
|
|
21
|
+
elif "corrupted" in str(e).lower() or "Invalid" in str(e):
|
|
22
|
+
console.print(f"[red]Vault file is corrupted: {escape(str(e))}[/red]")
|
|
23
|
+
console.print(f"[yellow]Contact {SUPPORT_EMAIL} with error code below if this persists.[/yellow]")
|
|
24
|
+
else:
|
|
25
|
+
console.print(f"[red]{escape(str(e))}[/red]")
|
|
26
|
+
raise SystemExit(1)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def require_vault(vault: VaultManager) -> None:
|
|
30
|
+
if not vault.exists():
|
|
31
|
+
console.print(f"[red]{VAULT_MISSING_MSG}[/red]")
|
|
32
|
+
raise SystemExit(1)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def unlock_vault(vault: VaultManager, prompt: str = "Master password") -> tuple:
|
|
36
|
+
"""Prompt, unlock, and convert any failure into a clean exit."""
|
|
37
|
+
from cryptography.exceptions import InvalidTag
|
|
38
|
+
|
|
39
|
+
password = click.prompt(prompt, hide_input=True)
|
|
40
|
+
try:
|
|
41
|
+
return vault.unlock(password), password
|
|
42
|
+
except (InvalidTag, ValueError) as e:
|
|
43
|
+
handle_vault_error(e)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from datetime import datetime, timezone
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
from cryptography.exceptions import InvalidTag
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.markup import escape
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
|
|
10
|
+
from ..core.vault import VaultManager
|
|
11
|
+
from ._shared import handle_vault_error, require_vault
|
|
12
|
+
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@click.command()
|
|
17
|
+
@click.option("--unused-days", default=90, type=int, help="Days of inactivity threshold")
|
|
18
|
+
@click.option("--dns", is_flag=True, help="Also check domain DNS resolution (makes network requests)")
|
|
19
|
+
@click.option("--json-output", "json_flag", is_flag=True, help="Output as JSON")
|
|
20
|
+
def audit(unused_days: int, dns: bool, json_flag: bool):
|
|
21
|
+
"""Audit vault for security issues (offline by default)."""
|
|
22
|
+
vault = VaultManager()
|
|
23
|
+
require_vault(vault)
|
|
24
|
+
|
|
25
|
+
from ._shared import unlock_vault
|
|
26
|
+
try:
|
|
27
|
+
(payload, _), _pw = unlock_vault(vault)
|
|
28
|
+
except (InvalidTag, ValueError) as e:
|
|
29
|
+
handle_vault_error(e)
|
|
30
|
+
|
|
31
|
+
issues = []
|
|
32
|
+
now = datetime.now(timezone.utc)
|
|
33
|
+
|
|
34
|
+
for c in payload.credentials:
|
|
35
|
+
if c.last_used_at:
|
|
36
|
+
try:
|
|
37
|
+
last = datetime.fromisoformat(c.last_used_at)
|
|
38
|
+
except ValueError:
|
|
39
|
+
continue
|
|
40
|
+
if (now - last).days >= unused_days:
|
|
41
|
+
issues.append({
|
|
42
|
+
"type": "stale",
|
|
43
|
+
"severity": "low",
|
|
44
|
+
"rp_id": c.rp_id,
|
|
45
|
+
"username": c.username,
|
|
46
|
+
"detail": f"Not used in {(now - last).days} days",
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
seen = {}
|
|
50
|
+
for c in payload.credentials:
|
|
51
|
+
key = (c.rp_id, c.username)
|
|
52
|
+
if key in seen:
|
|
53
|
+
issues.append({
|
|
54
|
+
"type": "duplicate",
|
|
55
|
+
"severity": "medium",
|
|
56
|
+
"rp_id": c.rp_id,
|
|
57
|
+
"username": c.username,
|
|
58
|
+
"detail": "Multiple credentials for same rpId+username",
|
|
59
|
+
})
|
|
60
|
+
seen[key] = True
|
|
61
|
+
|
|
62
|
+
# DNS resolution is OPT-IN: it sends every stored domain to the system
|
|
63
|
+
# resolver, which leaks the user's site list. Default is offline.
|
|
64
|
+
if dns:
|
|
65
|
+
import socket
|
|
66
|
+
for domain in {c.rp_id for c in payload.credentials}:
|
|
67
|
+
try:
|
|
68
|
+
socket.getaddrinfo(domain, 443, socket.AF_INET, socket.SOCK_STREAM)
|
|
69
|
+
except socket.gaierror:
|
|
70
|
+
issues.append({
|
|
71
|
+
"type": "unresolvable",
|
|
72
|
+
"severity": "high",
|
|
73
|
+
"rp_id": domain,
|
|
74
|
+
"username": "",
|
|
75
|
+
"detail": f"Cannot resolve {domain}",
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
if json_flag:
|
|
79
|
+
console.print_json(json.dumps(issues, indent=2))
|
|
80
|
+
else:
|
|
81
|
+
if not issues:
|
|
82
|
+
console.print("[green]No issues found. Vault looks good.[/green]")
|
|
83
|
+
raise SystemExit(0)
|
|
84
|
+
|
|
85
|
+
table = Table(title=f"Audit Issues ({len(issues)})")
|
|
86
|
+
table.add_column("Severity", style="yellow")
|
|
87
|
+
table.add_column("Type", style="cyan")
|
|
88
|
+
table.add_column("Domain", style="white")
|
|
89
|
+
table.add_column("Detail", style="white")
|
|
90
|
+
for issue in issues:
|
|
91
|
+
table.add_row(escape(issue["severity"]), escape(issue["type"]), escape(issue["rp_id"]), escape(issue["detail"]))
|
|
92
|
+
console.print(table)
|
|
93
|
+
raise SystemExit(1)
|