capauth 0.2.3__tar.gz → 0.2.14__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.
Files changed (158) hide show
  1. {capauth-0.2.3/src/capauth.egg-info → capauth-0.2.14}/PKG-INFO +69 -1
  2. {capauth-0.2.3 → capauth-0.2.14}/README.md +61 -0
  3. {capauth-0.2.3 → capauth-0.2.14}/pyproject.toml +8 -1
  4. capauth-0.2.14/src/capauth/__init__.py +177 -0
  5. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/agent_identity.py +28 -8
  6. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/authentik/claims_mapper.py +1 -1
  7. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/authentik/stage.py +70 -28
  8. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/authentik/verifier.py +121 -9
  9. capauth-0.2.14/src/capauth/authz.py +380 -0
  10. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/cli.py +478 -0
  11. capauth-0.2.14/src/capauth/crypto/__init__.py +91 -0
  12. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/crypto/base.py +57 -2
  13. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/crypto/gnupg_backend.py +28 -3
  14. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/crypto/pgpy_backend.py +64 -2
  15. capauth-0.2.14/src/capauth/crypto/sequoia_backend.py +435 -0
  16. capauth-0.2.14/src/capauth/crypto/skpgp_backend.py +220 -0
  17. capauth-0.2.14/src/capauth/custody.py +638 -0
  18. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/did.py +34 -6
  19. capauth-0.2.14/src/capauth/exceptions.py +68 -0
  20. capauth-0.2.14/src/capauth/identity.py +256 -0
  21. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/integration.py +41 -12
  22. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/integrations/forgejo/cli.py +8 -2
  23. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/integrations/forgejo/forgejo_api.py +2 -1
  24. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/integrations/forgejo/oidc_provider.py +4 -4
  25. capauth-0.2.14/src/capauth/integrations/nextcloud/tests/oidc/oidc_e2e_login_test.py +38 -0
  26. capauth-0.2.14/src/capauth/key_transparency.py +433 -0
  27. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/login.py +2 -2
  28. capauth-0.2.14/src/capauth/manifest.py +674 -0
  29. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/migrations/0001_initial.py +3 -1
  30. capauth-0.2.14/src/capauth/migrations/0002_widen_fingerprint_to_64.py +40 -0
  31. capauth-0.2.14/src/capauth/models.py +201 -0
  32. capauth-0.2.14/src/capauth/pairing/__init__.py +68 -0
  33. capauth-0.2.14/src/capauth/pairing/kernel.py +234 -0
  34. capauth-0.2.14/src/capauth/pairing/records.py +182 -0
  35. capauth-0.2.14/src/capauth/pairing/store.py +320 -0
  36. capauth-0.2.14/src/capauth/pairing/window.py +156 -0
  37. capauth-0.2.14/src/capauth/pqc_confidentiality.py +82 -0
  38. capauth-0.2.14/src/capauth/pqc_identity.py +220 -0
  39. capauth-0.2.14/src/capauth/pqc_root_identity.py +315 -0
  40. capauth-0.2.14/src/capauth/provisioning.py +80 -0
  41. capauth-0.2.14/src/capauth/seal.py +552 -0
  42. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/service/app.py +322 -9
  43. capauth-0.2.14/src/capauth/service/bunker.py +532 -0
  44. capauth-0.2.14/src/capauth/service/bunker_e2e.py +174 -0
  45. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/service/keystore.py +3 -3
  46. capauth-0.2.14/src/capauth/service/oidc/__init__.py +33 -0
  47. capauth-0.2.14/src/capauth/service/oidc/clients.py +111 -0
  48. capauth-0.2.14/src/capauth/service/oidc/passkey.py +205 -0
  49. capauth-0.2.14/src/capauth/service/oidc/provider.py +1049 -0
  50. capauth-0.2.14/src/capauth/service/oidc/signing_key.py +134 -0
  51. capauth-0.2.14/src/capauth/service/oidc/store.py +211 -0
  52. capauth-0.2.14/src/capauth/service/push.py +173 -0
  53. capauth-0.2.14/src/capauth/tokens.py +732 -0
  54. capauth-0.2.14/src/capauth/trust/__init__.py +53 -0
  55. capauth-0.2.14/src/capauth/trust/calibration.py +221 -0
  56. capauth-0.2.14/src/capauth/trust/graph.py +516 -0
  57. {capauth-0.2.3 → capauth-0.2.14/src/capauth.egg-info}/PKG-INFO +69 -1
  58. {capauth-0.2.3 → capauth-0.2.14}/src/capauth.egg-info/SOURCES.txt +66 -0
  59. {capauth-0.2.3 → capauth-0.2.14}/src/capauth.egg-info/requires.txt +6 -0
  60. capauth-0.2.14/tests/conftest.py +83 -0
  61. capauth-0.2.14/tests/test_agent_identity.py +486 -0
  62. capauth-0.2.14/tests/test_audience_mint_helper.py +211 -0
  63. capauth-0.2.14/tests/test_audience_tokens.py +265 -0
  64. capauth-0.2.14/tests/test_authz.py +273 -0
  65. capauth-0.2.14/tests/test_backup_restore.py +304 -0
  66. capauth-0.2.14/tests/test_bunker.py +463 -0
  67. capauth-0.2.14/tests/test_bunker_e2e.py +143 -0
  68. capauth-0.2.14/tests/test_capauth_service_deploy.py +215 -0
  69. capauth-0.2.14/tests/test_custody.py +419 -0
  70. capauth-0.2.14/tests/test_did_capability_relationships.py +94 -0
  71. capauth-0.2.14/tests/test_fingerprint_v6.py +75 -0
  72. capauth-0.2.14/tests/test_forgejo_preflight.py +111 -0
  73. capauth-0.2.14/tests/test_identity_ttl.py +292 -0
  74. capauth-0.2.14/tests/test_key_revocation_expiry.py +263 -0
  75. capauth-0.2.14/tests/test_key_transparency.py +171 -0
  76. capauth-0.2.14/tests/test_manifest.py +402 -0
  77. capauth-0.2.14/tests/test_models_pqc_q0.py +90 -0
  78. capauth-0.2.14/tests/test_no_subapp_dependency.py +140 -0
  79. capauth-0.2.14/tests/test_oidc_idp.py +469 -0
  80. capauth-0.2.14/tests/test_oidc_idp_e2e.py +178 -0
  81. capauth-0.2.14/tests/test_pairing.py +450 -0
  82. capauth-0.2.14/tests/test_passkey.py +137 -0
  83. capauth-0.2.14/tests/test_pqc_confidentiality.py +44 -0
  84. capauth-0.2.14/tests/test_pqc_identity.py +121 -0
  85. capauth-0.2.14/tests/test_pqc_root_integration.py +118 -0
  86. capauth-0.2.14/tests/test_pqc_t3_gate.py +526 -0
  87. capauth-0.2.14/tests/test_provisioning.py +39 -0
  88. capauth-0.2.14/tests/test_push.py +76 -0
  89. capauth-0.2.14/tests/test_py313_compat.py +74 -0
  90. {capauth-0.2.3 → capauth-0.2.14}/tests/test_qr_login.py +12 -4
  91. {capauth-0.2.3 → capauth-0.2.14}/tests/test_register_cli.py +9 -0
  92. capauth-0.2.14/tests/test_seal.py +324 -0
  93. capauth-0.2.14/tests/test_sequoia_backend.py +161 -0
  94. capauth-0.2.14/tests/test_service_v6_fingerprint.py +87 -0
  95. capauth-0.2.14/tests/test_skpgp_backend.py +143 -0
  96. capauth-0.2.14/tests/test_tokens.py +263 -0
  97. capauth-0.2.14/tests/test_trust_calibration.py +289 -0
  98. capauth-0.2.14/tests/test_trust_graph.py +238 -0
  99. capauth-0.2.14/tests/test_verifier.py +246 -0
  100. capauth-0.2.3/src/capauth/__init__.py +0 -48
  101. capauth-0.2.3/src/capauth/crypto/__init__.py +0 -42
  102. capauth-0.2.3/src/capauth/exceptions.py +0 -33
  103. capauth-0.2.3/src/capauth/identity.py +0 -130
  104. capauth-0.2.3/src/capauth/models.py +0 -101
  105. capauth-0.2.3/tests/conftest.py +0 -46
  106. capauth-0.2.3/tests/test_agent_identity.py +0 -251
  107. capauth-0.2.3/tests/test_verifier.py +0 -120
  108. {capauth-0.2.3 → capauth-0.2.14}/LICENSE +0 -0
  109. {capauth-0.2.3 → capauth-0.2.14}/MANIFEST.in +0 -0
  110. {capauth-0.2.3 → capauth-0.2.14}/setup.cfg +0 -0
  111. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/apps.py +0 -0
  112. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/authentik/__init__.py +0 -0
  113. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/authentik/api.py +0 -0
  114. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/authentik/nonce_store.py +0 -0
  115. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/authentik/urls.py +0 -0
  116. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/discovery/__init__.py +0 -0
  117. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/discovery/base.py +0 -0
  118. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/discovery/file_discovery.py +0 -0
  119. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/discovery/mdns.py +0 -0
  120. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/discovery/syncthing.py +0 -0
  121. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/integrations/__init__.py +0 -0
  122. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/integrations/forgejo/__init__.py +0 -0
  123. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/integrations/forgejo/auth_flow.py +0 -0
  124. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/integrations/forgejo/config.py +0 -0
  125. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/integrations/forgejo/tests/__init__.py +0 -0
  126. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/integrations/forgejo/tests/test_auth_flow.py +0 -0
  127. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/integrations/forgejo/tests/test_cli.py +0 -0
  128. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/integrations/forgejo/tests/test_config.py +0 -0
  129. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/integrations/forgejo/tests/test_forgejo_api.py +0 -0
  130. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/integrations/forgejo/tests/test_oidc_provider.py +0 -0
  131. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/mesh.py +0 -0
  132. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/migrations/__init__.py +0 -0
  133. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/pma.py +0 -0
  134. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/profile.py +0 -0
  135. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/registry.py +0 -0
  136. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/service/__init__.py +0 -0
  137. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/service/server.py +0 -0
  138. {capauth-0.2.3 → capauth-0.2.14}/src/capauth/sync.py +0 -0
  139. {capauth-0.2.3 → capauth-0.2.14}/src/capauth.egg-info/dependency_links.txt +0 -0
  140. {capauth-0.2.3 → capauth-0.2.14}/src/capauth.egg-info/entry_points.txt +0 -0
  141. {capauth-0.2.3 → capauth-0.2.14}/src/capauth.egg-info/top_level.txt +0 -0
  142. {capauth-0.2.3 → capauth-0.2.14}/tests/__init__.py +0 -0
  143. {capauth-0.2.3 → capauth-0.2.14}/tests/test_claims_mapper.py +0 -0
  144. {capauth-0.2.3 → capauth-0.2.14}/tests/test_crypto.py +0 -0
  145. {capauth-0.2.3 → capauth-0.2.14}/tests/test_home_resolution.py +0 -0
  146. {capauth-0.2.3 → capauth-0.2.14}/tests/test_identity.py +0 -0
  147. {capauth-0.2.3 → capauth-0.2.14}/tests/test_integration.py +0 -0
  148. {capauth-0.2.3 → capauth-0.2.14}/tests/test_login.py +0 -0
  149. {capauth-0.2.3 → capauth-0.2.14}/tests/test_mesh.py +0 -0
  150. {capauth-0.2.3 → capauth-0.2.14}/tests/test_nextcloud_app.py +0 -0
  151. {capauth-0.2.3 → capauth-0.2.14}/tests/test_nonce_store.py +0 -0
  152. {capauth-0.2.3 → capauth-0.2.14}/tests/test_pma.py +0 -0
  153. {capauth-0.2.3 → capauth-0.2.14}/tests/test_profile.py +0 -0
  154. {capauth-0.2.3 → capauth-0.2.14}/tests/test_real_pgp_e2e.py +0 -0
  155. {capauth-0.2.3 → capauth-0.2.14}/tests/test_registry.py +0 -0
  156. {capauth-0.2.3 → capauth-0.2.14}/tests/test_service.py +0 -0
  157. {capauth-0.2.3 → capauth-0.2.14}/tests/test_syncthing_discovery.py +0 -0
  158. {capauth-0.2.3 → capauth-0.2.14}/tests/test_zk_profile.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: capauth
3
- Version: 0.2.3
3
+ Version: 0.2.14
4
4
  Summary: Capability-based Authentication — decentralized, PGP-based identity and authorization
5
5
  Author-email: smilinTux <admin@smilintux.org>
6
6
  License: GPL-3.0-or-later
@@ -18,6 +18,7 @@ Classifier: Programming Language :: Python :: 3
18
18
  Classifier: Programming Language :: Python :: 3.10
19
19
  Classifier: Programming Language :: Python :: 3.11
20
20
  Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
21
22
  Classifier: Topic :: Security :: Cryptography
22
23
  Requires-Python: >=3.10
23
24
  Description-Content-Type: text/markdown
@@ -35,10 +36,13 @@ Requires-Dist: python-gnupg>=0.5; extra == "gnupg"
35
36
  Provides-Extra: service
36
37
  Requires-Dist: fastapi>=0.110; extra == "service"
37
38
  Requires-Dist: uvicorn[standard]>=0.27; extra == "service"
39
+ Requires-Dist: python-multipart>=0.0.6; extra == "service"
38
40
  Requires-Dist: PyJWT>=2.8; extra == "service"
39
41
  Requires-Dist: python-gnupg>=0.5; extra == "service"
40
42
  Requires-Dist: httpx>=0.25; extra == "service"
41
43
  Requires-Dist: segno>=1.6; extra == "service"
44
+ Requires-Dist: pywebpush>=1.14; extra == "service"
45
+ Requires-Dist: webauthn>=2.0; extra == "service"
42
46
  Provides-Extra: dev
43
47
  Requires-Dist: pytest>=7.0; extra == "dev"
44
48
  Requires-Dist: pytest-cov>=4.0; extra == "dev"
@@ -49,12 +53,17 @@ Requires-Dist: python-gnupg>=0.5; extra == "dev"
49
53
  Requires-Dist: black>=24.0; extra == "dev"
50
54
  Requires-Dist: ruff>=0.3; extra == "dev"
51
55
  Requires-Dist: zeroconf>=0.38; extra == "dev"
56
+ Requires-Dist: webauthn>=2.0; extra == "dev"
57
+ Requires-Dist: soft-webauthn>=0.1; extra == "dev"
58
+ Requires-Dist: pywebpush>=1.14; extra == "dev"
52
59
  Provides-Extra: all
53
60
  Requires-Dist: capauth[dev,gnupg,service]; extra == "all"
54
61
  Dynamic: license-file
55
62
 
56
63
  # capauth — Sovereign PGP Identity 🔐
57
64
 
65
+ [![pytest](https://github.com/smilinTux/capauth/actions/workflows/pytest.yml/badge.svg)](https://github.com/smilinTux/capauth/actions/workflows/pytest.yml)
66
+
58
67
  > **OAuth is dead. Long live sovereignty.**
59
68
  > Your identity is a PGP keypair *you* generated, on hardware *you* own. No
60
69
  > "Login with Google", no authorization server in the middle, no revocation risk
@@ -73,6 +82,20 @@ bearer token issued by a third party, you sign a random challenge with a key onl
73
82
  you hold. The verifier checks the signature against your public key. Valid
74
83
  signature = authenticated. Done. No middleman ever sees the secret.
75
84
 
85
+ > **Maturity tier: T0 (live).** The live sovereign root and the agent
86
+ > signing / DID / challenge-response keys are **classical** Ed25519 / RSA-4096
87
+ > (RFC 8032 / 4880) — Shor-breakable once a CRQC exists. capauth is a
88
+ > **signature / identity** layer (not a KEM), so signatures are not retroactively
89
+ > breakable and Harvest-Now-Decrypt-Later does not apply — migration is real but
90
+ > **deferrable**. The **T3 hybrid-signature path is additive and proven**: the
91
+ > Sequoia (`sq`) backend issues + verifies **ML-DSA-87 + Ed448** (FIPS 204) /
92
+ > **ML-KEM-1024 + X448** (FIPS 203) composite v6 keys end-to-end, but the live root
93
+ > stays classical until the gated root-rotation ceremony. **Honest claim:** this is
94
+ > *not* "quantum-proof" / "quantum-safe." See [SOP.md](SOP.md),
95
+ > [docs/CRYPTO_SPEC.md](docs/CRYPTO_SPEC.md), and the sk-standards
96
+ > [CRYPTOGRAPHY_STANDARD](https://github.com/smilinTux/sk-standards). Migration:
97
+ > epic `PQC-MIGRATION` (coord `e1d6ba2a`).
98
+
76
99
  ---
77
100
 
78
101
  ## The 60-second version
@@ -179,6 +202,31 @@ all Syncthing mesh nodes so every host shares one keypair.
179
202
  | **Integration generators** | One-shot config for third-party login, e.g. Forgejo OAuth2/OIDC (`capauth setup forgejo`) |
180
203
  | **skcapstone adapter** | Default-on-by-presence: routes auth events to `sk-alert`, registers a key-rotation check with `skscheduler` |
181
204
 
205
+ ### Challenge TTL and replay contract (`identity.verify_challenge`)
206
+
207
+ `verify_challenge` enforces a **max challenge age of 5 minutes by default**
208
+ (`DEFAULT_MAX_CHALLENGE_AGE_SECONDS = 300`); older challenges raise
209
+ `ChallengeExpiredError`. Tune it with `max_age_seconds=...`, or pass
210
+ `max_age_seconds=None` to opt out **only** when your layer enforces TTL
211
+ itself (the service layer does, via its nonce store).
212
+
213
+ **Within the TTL the bare primitive is replayable**: the same signed
214
+ response verifies repeatedly unless you track seen challenge ids. For
215
+ single-use semantics pass a `replay_guard`:
216
+
217
+ ```python
218
+ from capauth.identity import InMemoryReplayGuard, verify_challenge
219
+
220
+ guard = InMemoryReplayGuard() # single-process reference implementation
221
+ verify_challenge(challenge, response, pubkey, replay_guard=guard)
222
+ verify_challenge(challenge, response, pubkey, replay_guard=guard) # raises ChallengeReplayError
223
+ ```
224
+
225
+ Any `(challenge_id, expires_at) -> bool` callable works as a guard. For
226
+ durable / multi-node deployments use a real nonce store; the reference
227
+ implementation is `capauth.authentik.nonce_store.NonceStore` (what the
228
+ verification service uses).
229
+
182
230
  ## Key CLI commands
183
231
 
184
232
  ```bash
@@ -231,6 +279,8 @@ rides in the payload `event` field so routing stays severity-based.
231
279
  | **[Protocol](docs/PROTOCOL.md)** | the CapAuth wire protocol specification |
232
280
  | **[Claims](docs/CLAIMS.md)** | capability claims and token format |
233
281
  | **[Integration Blueprint](docs/INTEGRATION_BLUEPRINT.md)** | third-party integration guide |
282
+ | **[Cold-Machine Bootstrap & DR](docs/COLD_MACHINE_BOOTSTRAP_AND_DR.md)** | standing capauth back up on a blank box + disaster recovery: the restore-not-regenerate rule, the ordered restore chain, and the operator checklist |
283
+ | **[authentik-capauth](docs/authentik-capauth.md)** | the custom Authentik image with the CapAuth PGP stage baked in — build (`AK_VERSION`, version-agnostic venv install, frontend rebuild), SKStacks deploy + `lifecycle.migrate` override, and the four build/migrate gotchas |
234
284
  | **[AI Advocate](AI-ADVOCATE.md)** | how AI advocates manage a sovereign profile on your behalf |
235
285
 
236
286
  ## Why it matters
@@ -245,6 +295,24 @@ verification instantly instead of going undetected.
245
295
 
246
296
  > **"You are not a user. You are a sovereign."**
247
297
 
298
+ ## Related projects / See also
299
+
300
+ capauth is the identity root of SKWorld — most of the stack links back here.
301
+
302
+ - ⬇️ **Used by:** [skchat](https://github.com/smilinTux/skchat) — routes messages by
303
+ the identity capauth resolves (`resolve_agent_identity()`); per-agent signing key.
304
+ - ⬇️ **Used by:** [skcomms](https://github.com/smilinTux/skcomms) — capauth-signed
305
+ envelopes + FQID (`<a>@<op>.<realm>`) sovereign addressing.
306
+ - ↔️ **Sibling (PQC signing root):** [sk_pgp](https://github.com/smilinTux/sk_pgp) —
307
+ the sovereign OpenPGP-PQC library (Sequoia-backed) capauth's PQC root migrates onto.
308
+ - ↔️ **Sibling (hybrid KEM):** [sk-pqc](https://github.com/smilinTux/sk-pqc-py) — the
309
+ `HKDF(X25519 ‖ ML-KEM-768)` KEM for confidentiality; capauth provides the
310
+ authentication that a KEM-only library deliberately does not (pair them).
311
+ - ↔️ **Sibling (Security capability):** [sksecurity](https://github.com/smilinTux/sksecurity)
312
+ — produces the runtime crypto self-report that makes capauth's claims evidence-backed.
313
+ - 📐 **Standards:** [sk-standards](https://github.com/smilinTux/sk-standards) — the
314
+ crypto, data-flow, version, and doc/SOP standards (incl. `CRYPTOGRAPHY_STANDARD`).
315
+
248
316
  ## License
249
317
 
250
318
  **GPL-3.0-or-later** — Free as in freedom. Identity is a right, not a product.
@@ -1,5 +1,7 @@
1
1
  # capauth — Sovereign PGP Identity 🔐
2
2
 
3
+ [![pytest](https://github.com/smilinTux/capauth/actions/workflows/pytest.yml/badge.svg)](https://github.com/smilinTux/capauth/actions/workflows/pytest.yml)
4
+
3
5
  > **OAuth is dead. Long live sovereignty.**
4
6
  > Your identity is a PGP keypair *you* generated, on hardware *you* own. No
5
7
  > "Login with Google", no authorization server in the middle, no revocation risk
@@ -18,6 +20,20 @@ bearer token issued by a third party, you sign a random challenge with a key onl
18
20
  you hold. The verifier checks the signature against your public key. Valid
19
21
  signature = authenticated. Done. No middleman ever sees the secret.
20
22
 
23
+ > **Maturity tier: T0 (live).** The live sovereign root and the agent
24
+ > signing / DID / challenge-response keys are **classical** Ed25519 / RSA-4096
25
+ > (RFC 8032 / 4880) — Shor-breakable once a CRQC exists. capauth is a
26
+ > **signature / identity** layer (not a KEM), so signatures are not retroactively
27
+ > breakable and Harvest-Now-Decrypt-Later does not apply — migration is real but
28
+ > **deferrable**. The **T3 hybrid-signature path is additive and proven**: the
29
+ > Sequoia (`sq`) backend issues + verifies **ML-DSA-87 + Ed448** (FIPS 204) /
30
+ > **ML-KEM-1024 + X448** (FIPS 203) composite v6 keys end-to-end, but the live root
31
+ > stays classical until the gated root-rotation ceremony. **Honest claim:** this is
32
+ > *not* "quantum-proof" / "quantum-safe." See [SOP.md](SOP.md),
33
+ > [docs/CRYPTO_SPEC.md](docs/CRYPTO_SPEC.md), and the sk-standards
34
+ > [CRYPTOGRAPHY_STANDARD](https://github.com/smilinTux/sk-standards). Migration:
35
+ > epic `PQC-MIGRATION` (coord `e1d6ba2a`).
36
+
21
37
  ---
22
38
 
23
39
  ## The 60-second version
@@ -124,6 +140,31 @@ all Syncthing mesh nodes so every host shares one keypair.
124
140
  | **Integration generators** | One-shot config for third-party login, e.g. Forgejo OAuth2/OIDC (`capauth setup forgejo`) |
125
141
  | **skcapstone adapter** | Default-on-by-presence: routes auth events to `sk-alert`, registers a key-rotation check with `skscheduler` |
126
142
 
143
+ ### Challenge TTL and replay contract (`identity.verify_challenge`)
144
+
145
+ `verify_challenge` enforces a **max challenge age of 5 minutes by default**
146
+ (`DEFAULT_MAX_CHALLENGE_AGE_SECONDS = 300`); older challenges raise
147
+ `ChallengeExpiredError`. Tune it with `max_age_seconds=...`, or pass
148
+ `max_age_seconds=None` to opt out **only** when your layer enforces TTL
149
+ itself (the service layer does, via its nonce store).
150
+
151
+ **Within the TTL the bare primitive is replayable**: the same signed
152
+ response verifies repeatedly unless you track seen challenge ids. For
153
+ single-use semantics pass a `replay_guard`:
154
+
155
+ ```python
156
+ from capauth.identity import InMemoryReplayGuard, verify_challenge
157
+
158
+ guard = InMemoryReplayGuard() # single-process reference implementation
159
+ verify_challenge(challenge, response, pubkey, replay_guard=guard)
160
+ verify_challenge(challenge, response, pubkey, replay_guard=guard) # raises ChallengeReplayError
161
+ ```
162
+
163
+ Any `(challenge_id, expires_at) -> bool` callable works as a guard. For
164
+ durable / multi-node deployments use a real nonce store; the reference
165
+ implementation is `capauth.authentik.nonce_store.NonceStore` (what the
166
+ verification service uses).
167
+
127
168
  ## Key CLI commands
128
169
 
129
170
  ```bash
@@ -176,6 +217,8 @@ rides in the payload `event` field so routing stays severity-based.
176
217
  | **[Protocol](docs/PROTOCOL.md)** | the CapAuth wire protocol specification |
177
218
  | **[Claims](docs/CLAIMS.md)** | capability claims and token format |
178
219
  | **[Integration Blueprint](docs/INTEGRATION_BLUEPRINT.md)** | third-party integration guide |
220
+ | **[Cold-Machine Bootstrap & DR](docs/COLD_MACHINE_BOOTSTRAP_AND_DR.md)** | standing capauth back up on a blank box + disaster recovery: the restore-not-regenerate rule, the ordered restore chain, and the operator checklist |
221
+ | **[authentik-capauth](docs/authentik-capauth.md)** | the custom Authentik image with the CapAuth PGP stage baked in — build (`AK_VERSION`, version-agnostic venv install, frontend rebuild), SKStacks deploy + `lifecycle.migrate` override, and the four build/migrate gotchas |
179
222
  | **[AI Advocate](AI-ADVOCATE.md)** | how AI advocates manage a sovereign profile on your behalf |
180
223
 
181
224
  ## Why it matters
@@ -190,6 +233,24 @@ verification instantly instead of going undetected.
190
233
 
191
234
  > **"You are not a user. You are a sovereign."**
192
235
 
236
+ ## Related projects / See also
237
+
238
+ capauth is the identity root of SKWorld — most of the stack links back here.
239
+
240
+ - ⬇️ **Used by:** [skchat](https://github.com/smilinTux/skchat) — routes messages by
241
+ the identity capauth resolves (`resolve_agent_identity()`); per-agent signing key.
242
+ - ⬇️ **Used by:** [skcomms](https://github.com/smilinTux/skcomms) — capauth-signed
243
+ envelopes + FQID (`<a>@<op>.<realm>`) sovereign addressing.
244
+ - ↔️ **Sibling (PQC signing root):** [sk_pgp](https://github.com/smilinTux/sk_pgp) —
245
+ the sovereign OpenPGP-PQC library (Sequoia-backed) capauth's PQC root migrates onto.
246
+ - ↔️ **Sibling (hybrid KEM):** [sk-pqc](https://github.com/smilinTux/sk-pqc-py) — the
247
+ `HKDF(X25519 ‖ ML-KEM-768)` KEM for confidentiality; capauth provides the
248
+ authentication that a KEM-only library deliberately does not (pair them).
249
+ - ↔️ **Sibling (Security capability):** [sksecurity](https://github.com/smilinTux/sksecurity)
250
+ — produces the runtime crypto self-report that makes capauth's claims evidence-backed.
251
+ - 📐 **Standards:** [sk-standards](https://github.com/smilinTux/sk-standards) — the
252
+ crypto, data-flow, version, and doc/SOP standards (incl. `CRYPTOGRAPHY_STANDARD`).
253
+
193
254
  ## License
194
255
 
195
256
  **GPL-3.0-or-later** — Free as in freedom. Identity is a right, not a product.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "capauth"
7
- version = "0.2.3"
7
+ version = "0.2.14"
8
8
  description = "Capability-based Authentication — decentralized, PGP-based identity and authorization"
9
9
  readme = "README.md"
10
10
  license = {text = "GPL-3.0-or-later"}
@@ -22,6 +22,7 @@ classifiers = [
22
22
  "Programming Language :: Python :: 3.10",
23
23
  "Programming Language :: Python :: 3.11",
24
24
  "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
25
26
  "Topic :: Security :: Cryptography",
26
27
  ]
27
28
  dependencies = [
@@ -43,10 +44,13 @@ gnupg = [
43
44
  service = [
44
45
  "fastapi>=0.110",
45
46
  "uvicorn[standard]>=0.27",
47
+ "python-multipart>=0.0.6",
46
48
  "PyJWT>=2.8",
47
49
  "python-gnupg>=0.5",
48
50
  "httpx>=0.25",
49
51
  "segno>=1.6",
52
+ "pywebpush>=1.14",
53
+ "webauthn>=2.0",
50
54
  ]
51
55
  dev = [
52
56
  "pytest>=7.0",
@@ -58,6 +62,9 @@ dev = [
58
62
  "black>=24.0",
59
63
  "ruff>=0.3",
60
64
  "zeroconf>=0.38",
65
+ "webauthn>=2.0",
66
+ "soft-webauthn>=0.1",
67
+ "pywebpush>=1.14",
61
68
  ]
62
69
  all = [
63
70
  "capauth[gnupg,service,dev]",
@@ -0,0 +1,177 @@
1
+ """CapAuth -- Capability-based Authentication.
2
+
3
+ Decentralized, PGP-based identity and authorization.
4
+ OAuth is dead. Long live sovereignty.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ from pathlib import Path
11
+
12
+ from .agent_identity import AgentIdentity, resolve_agent_identity
13
+ from .manifest import (
14
+ DEFAULT_SIG_SUFFIX,
15
+ ManifestSigningError,
16
+ canonical_manifest_bytes,
17
+ is_canonical,
18
+ operator_fingerprint,
19
+ sign_manifest,
20
+ verify_manifest,
21
+ )
22
+ from .authz import (
23
+ DEFAULT_RULES,
24
+ OBLIGATION_AUDIT,
25
+ CapabilityRule,
26
+ Decision,
27
+ Obligation,
28
+ decide,
29
+ )
30
+ from .pairing import (
31
+ DeviceRecord,
32
+ Enrollment,
33
+ EnrollmentMode,
34
+ PairingError,
35
+ PairingStore,
36
+ PairingWindow,
37
+ approve,
38
+ enroll_device,
39
+ list_devices,
40
+ mode_satisfies,
41
+ open_window,
42
+ revoke,
43
+ )
44
+ from .provisioning import SKCHAT_SCOPES, provision_subject
45
+ from .tokens import (
46
+ AUDIENCE_SCOPES,
47
+ Capability,
48
+ SignedToken,
49
+ TokenPayload,
50
+ TokenType,
51
+ export_token,
52
+ has_scope,
53
+ import_token,
54
+ is_revoked,
55
+ issue_token,
56
+ list_tokens,
57
+ mint_agent_audience_token,
58
+ mint_audience_token,
59
+ revoke_token,
60
+ verify_audience_token,
61
+ verify_token,
62
+ )
63
+ from .trust import (
64
+ CALIBRATION_FILENAME,
65
+ DEFAULT_THRESHOLDS,
66
+ FORMATTERS,
67
+ TrustEdge,
68
+ TrustGraph,
69
+ TrustNode,
70
+ TrustThresholds,
71
+ apply_setting,
72
+ build_trust_graph,
73
+ format_dot,
74
+ format_json,
75
+ format_table,
76
+ load_calibration,
77
+ recommend_thresholds,
78
+ save_calibration,
79
+ )
80
+
81
+ __all__ = [
82
+ "AgentIdentity",
83
+ "resolve_agent_identity",
84
+ "resolve_capauth_home",
85
+ # manifest signing (umbrella-shell signed module registry, section 5.3)
86
+ "sign_manifest",
87
+ "verify_manifest",
88
+ "canonical_manifest_bytes",
89
+ "is_canonical",
90
+ "operator_fingerprint",
91
+ "ManifestSigningError",
92
+ "DEFAULT_SIG_SUFFIX",
93
+ # authz kernel (spine M3: deterministic decide() PDP)
94
+ "decide",
95
+ "Decision",
96
+ "Obligation",
97
+ "CapabilityRule",
98
+ "DEFAULT_RULES",
99
+ "OBLIGATION_AUDIT",
100
+ # pairing kernel (spine M2: enrollment modes + operator window)
101
+ "enroll_device",
102
+ "approve",
103
+ "revoke",
104
+ "list_devices",
105
+ "open_window",
106
+ "mode_satisfies",
107
+ "PairingError",
108
+ "EnrollmentMode",
109
+ "Enrollment",
110
+ "DeviceRecord",
111
+ "PairingWindow",
112
+ "PairingStore",
113
+ # tokens (kernel track M1: moved verbatim from skcapstone)
114
+ "Capability",
115
+ "SignedToken",
116
+ "TokenPayload",
117
+ "TokenType",
118
+ "export_token",
119
+ "import_token",
120
+ "is_revoked",
121
+ "issue_token",
122
+ "list_tokens",
123
+ "revoke_token",
124
+ "verify_token",
125
+ # audience-scoped token minting (audience-mint, M1+/R4.2)
126
+ "AUDIENCE_SCOPES",
127
+ "mint_audience_token",
128
+ "mint_agent_audience_token",
129
+ "verify_audience_token",
130
+ "has_scope",
131
+ # trust (kernel track M1: moved verbatim from skcapstone)
132
+ "FORMATTERS",
133
+ "CALIBRATION_FILENAME",
134
+ "DEFAULT_THRESHOLDS",
135
+ "TrustEdge",
136
+ "TrustGraph",
137
+ "TrustNode",
138
+ "TrustThresholds",
139
+ "apply_setting",
140
+ "build_trust_graph",
141
+ "format_dot",
142
+ "format_json",
143
+ "format_table",
144
+ "load_calibration",
145
+ "recommend_thresholds",
146
+ "save_calibration",
147
+ ]
148
+
149
+ __version__ = "0.2.14"
150
+
151
+ SKCAPSTONE_HOME = Path.home() / ".skcapstone"
152
+ DEFAULT_CAPAUTH_DIR = SKCAPSTONE_HOME / "capauth"
153
+ LEGACY_CAPAUTH_DIR = Path.home() / ".capauth"
154
+
155
+
156
+ def resolve_capauth_home(base_dir: Path | None = None) -> Path:
157
+ """Resolve the CapAuth home directory.
158
+
159
+ Priority:
160
+ 1. Explicit ``base_dir``
161
+ 2. ``CAPAUTH_HOME`` environment override
162
+ 3. New default under ``~/.skcapstone/capauth`` when present
163
+ 4. Legacy ``~/.capauth`` when it already exists
164
+ 5. New default under ``~/.skcapstone/capauth``
165
+ """
166
+ if base_dir is not None:
167
+ return Path(base_dir).expanduser()
168
+
169
+ env_home = os.environ.get("CAPAUTH_HOME")
170
+ if env_home:
171
+ return Path(env_home).expanduser()
172
+
173
+ if DEFAULT_CAPAUTH_DIR.exists():
174
+ return DEFAULT_CAPAUTH_DIR
175
+ if LEGACY_CAPAUTH_DIR.exists():
176
+ return LEGACY_CAPAUTH_DIR
177
+ return DEFAULT_CAPAUTH_DIR
@@ -24,8 +24,9 @@ A resolved identity carries **two complementary URIs**:
24
24
  cluster.json is absent or malformed.
25
25
 
26
26
  ``fingerprint``
27
- 40-char PGP fingerprint from the agent's CapAuth profile. ``None`` when
28
- no real profile exists (placeholder identities are not surfaced here).
27
+ 40 (v4) or 64 (v6) hex PGP fingerprint from the agent's CapAuth profile.
28
+ ``None`` when no real profile exists (placeholder identities are not
29
+ surfaced here).
29
30
 
30
31
  Public API
31
32
  ----------
@@ -85,8 +86,8 @@ class AgentIdentity:
85
86
  fqid: Three-tier label — ``<agent>@<operator>.<realm>``
86
87
  (e.g. ``"lumina@chef.skworld"``). ``None`` when
87
88
  cluster.json is not available.
88
- fingerprint: 40-char PGP fingerprint from the agent's CapAuth
89
- profile. ``None`` when no real profile exists.
89
+ fingerprint: 40 (v4) or 64 (v6) hex PGP fingerprint from the agent's
90
+ CapAuth profile. ``None`` when no real profile exists.
90
91
  """
91
92
 
92
93
  agent: str
@@ -110,6 +111,25 @@ class AgentIdentity:
110
111
  "fingerprint": self.fingerprint,
111
112
  }
112
113
 
114
+ def hybrid_prekey_available(self) -> bool:
115
+ """Whether this agent advertises a hybrid PQ confidentiality prekey.
116
+
117
+ Honest capability lookup for the PQC cut-over (see
118
+ :mod:`capauth.pqc_confidentiality`): ``True`` only when a real
119
+ X25519+ML-KEM-768 prekey exists for this agent; ``False`` (classical /
120
+ negotiated downgrade) otherwise. CapAuth does not generate these keys
121
+ (Phase 2) — it reports them.
122
+ """
123
+ from .pqc_confidentiality import hybrid_prekey_available
124
+
125
+ return hybrid_prekey_available(self.agent)
126
+
127
+ def confidentiality_suite(self) -> str:
128
+ """The confidentiality suite a peer negotiates TO this agent today."""
129
+ from .pqc_confidentiality import confidentiality_suite_for
130
+
131
+ return confidentiality_suite_for(self.agent)
132
+
113
133
 
114
134
  # ---------------------------------------------------------------------------
115
135
  # Internal helpers
@@ -173,7 +193,7 @@ def _load_fingerprint(agent: str) -> Optional[str]:
173
193
  agent: Short agent name.
174
194
 
175
195
  Returns:
176
- 40-char hex fingerprint string, or None.
196
+ 40 (v4) or 64 (v6) hex fingerprint string, or None.
177
197
  """
178
198
  # 1. CapAuth profile.json (source of truth for real PGP profiles)
179
199
  capauth_dir = _agent_capauth_dir(agent)
@@ -182,7 +202,7 @@ def _load_fingerprint(agent: str) -> Optional[str]:
182
202
  try:
183
203
  data = json.loads(profile_path.read_text(encoding="utf-8"))
184
204
  fp = data.get("key_info", {}).get("fingerprint")
185
- if isinstance(fp, str) and len(fp) == 40:
205
+ if isinstance(fp, str) and len(fp) in (40, 64):
186
206
  return fp
187
207
  except (json.JSONDecodeError, OSError) as exc:
188
208
  logger.debug("profile.json parse failed for %s: %s", agent, exc)
@@ -193,7 +213,7 @@ def _load_fingerprint(agent: str) -> Optional[str]:
193
213
 
194
214
  profile = load_profile(base_dir=capauth_dir)
195
215
  fp = profile.key_info.fingerprint
196
- if isinstance(fp, str) and len(fp) == 40:
216
+ if isinstance(fp, str) and len(fp) in (40, 64):
197
217
  return fp
198
218
  except ImportError:
199
219
  pass
@@ -207,7 +227,7 @@ def _load_fingerprint(agent: str) -> Optional[str]:
207
227
  data = json.loads(identity_path.read_text(encoding="utf-8"))
208
228
  fp = data.get("fingerprint")
209
229
  # Only return if it looks like a real fingerprint (not placeholder)
210
- if isinstance(fp, str) and len(fp) == 40:
230
+ if isinstance(fp, str) and len(fp) in (40, 64):
211
231
  # Reject placeholder fingerprints (all caps hex, but generated
212
232
  # from SHA256 of "skcapstone:<name>")
213
233
  if data.get("capauth_managed", False):
@@ -113,7 +113,7 @@ def preferred_username_fallback(fingerprint: str) -> str:
113
113
  """Generate a stable display handle from a fingerprint when no name is asserted.
114
114
 
115
115
  Args:
116
- fingerprint: Full 40-char PGP fingerprint.
116
+ fingerprint: Full PGP fingerprint: 40 (v4) or 64 (v6) hex.
117
117
 
118
118
  Returns:
119
119
  str: Short handle like ``capauth-8A3FC2D1``.