Harden Sigil: README.md
This commit is contained in:
parent
f4a044f1d6
commit
5e1db8afcd
65
README.md
65
README.md
@ -11,8 +11,8 @@ a passcode is the identity -> can be copied, leaked in a URL, replayed
|
||||
a signature proves the identity -> cannot be forged, cannot be replayed
|
||||
```
|
||||
|
||||
This is a working reference implementation with tests. It is **not hardened for
|
||||
production** — see *Status* below.
|
||||
This is a working implementation with tests and a hardening pass behind it. It
|
||||
is **not independently reviewed** — see *Status* below.
|
||||
|
||||
## What it does
|
||||
|
||||
@ -31,15 +31,17 @@ export SIGIL_DB=/tmp/demo.db
|
||||
|
||||
python3 sigil.py seed bigpickle # -> one-time secret
|
||||
python3 sigil.py keygen /tmp/demo.key # -> public_b64, fingerprint
|
||||
# add a passphrase to encrypt
|
||||
python3 sigil.py enrol bigpickle "$PUB" "$SECRET" # bind key, consume secret
|
||||
python3 sigil.py challenge bigpickle # -> nonce
|
||||
python3 sigil.py sign /tmp/demo.key "$NONCE" # -> signature
|
||||
python3 sigil.py verify "$NONCE" "$SIG" # -> agent session token
|
||||
|
||||
python3 sigil.py code bigpickle # hand a human a code
|
||||
python3 sigil.py code bigpickle /tmp/demo.key # hand a human a code
|
||||
python3 sigil.py redeem X3ZB-NTLG # -> human_via_agent token
|
||||
python3 sigil.py whoami "$TOKEN" # kind, scopes, may_act_as_agent
|
||||
python3 sigil.py events bigpickle # audit trail
|
||||
python3 sigil.py verify-chain # is the log intact?
|
||||
```
|
||||
|
||||
## Tests
|
||||
@ -48,9 +50,10 @@ python3 sigil.py events bigpickle # audit trail
|
||||
python3 -m unittest discover -s tests -v
|
||||
```
|
||||
|
||||
54 tests. They cover the refusals, not just the happy paths: replayed
|
||||
80 tests. They cover the refusals, not just the happy paths: replayed
|
||||
signatures, wrong keys, expired nonces, single-use codes, scope escalation, and
|
||||
the invariant below.
|
||||
the invariant below. `test_hardening.py` has one test per defect found in
|
||||
review, so any of them regressing brings that defect back.
|
||||
|
||||
## The invariant
|
||||
|
||||
@ -76,21 +79,51 @@ session. Scopes come from the issuer and never widen with time. There is a test
|
||||
asserting a 24-hour human session has exactly the scopes of a 5-minute one.
|
||||
|
||||
**Code lifetime ≠ session lifetime.** A code is a key and expires in 5 minutes.
|
||||
The session it opens lasts an hour. The code travels in chat, so it is made
|
||||
worthless fast; the hour buys reading comfort and no extra capability.
|
||||
The session it opens lasts an hour, counted **from redemption**. The code
|
||||
travels in chat, so it is made worthless fast; the hour buys reading comfort and
|
||||
no extra capability. Waiting for a code does not eat into it — an earlier draft
|
||||
anchored the session to issue time, which quietly handed out less than promised.
|
||||
|
||||
**Nothing is minted from a name.** `issue_code` takes an agent *session*, not an
|
||||
agent id, and `verify_chain` exists so tampering is detectable. A function that
|
||||
accepts a string and returns authority is how "who is this?" quietly becomes
|
||||
unanswerable.
|
||||
|
||||
## Status
|
||||
|
||||
Reference implementation. Deliberately not yet:
|
||||
A hardening pass closed the defects below. Each is now covered by a test.
|
||||
|
||||
- **Reviewed by anyone.** This is security-critical code. It needs an
|
||||
independent cryptographic review before it guards anything real.
|
||||
- **Hardened at rest.** Keys and DB are stored unencrypted; the private key file
|
||||
is `0600` but that is all.
|
||||
- **Rate limited.** `challenge` is unmetered, so brute-force and
|
||||
nonce-exhaustion are open.
|
||||
- **Transport.** No TLS concerns addressed; assumes a trusted channel.
|
||||
- **Agent revocation** exists but has no expiry semantics.
|
||||
| Found in review | Now |
|
||||
|---|---|
|
||||
| `issue_code()` trusted an agent name, so it minted authority from a string | Takes an agent session; agent derived from it |
|
||||
| One-time code redeemable many times in a race (753 times in 200 trials) | Atomic compare-and-set; 300 races, one success each |
|
||||
| Bootstrap secret spendable more than once | Atomic compare-and-set |
|
||||
| Signed nonce verifiable more than once | Nonce claimed before the signature is judged |
|
||||
| Session tokens stored in clear | Stored as SHA-256 digests, like codes |
|
||||
| Audit log editable and deletable in place | Hash-chained; `verify_chain` names the first bad event |
|
||||
| `challenge` unmetered | Sliding-window limits per agent on every entry point |
|
||||
| Private keys written unencrypted | Optional passphrase (PBKDF2, AES-256), `0600` |
|
||||
| Rekey left old sessions alive | Rekey revokes every session for that agent |
|
||||
| Agent ids unvalidated, could forge log lines | Constrained alphabet and length |
|
||||
| 1-hour session anchored to issue time | Anchored to redemption |
|
||||
|
||||
Still open, and these are the reasons not to ship it yet:
|
||||
|
||||
- **No independent review.** Everything above is self-review. Self-review finds
|
||||
the bugs you thought of. It does not find the ones you did not. This needs
|
||||
someone else, ideally a cryptographer, before it guards anything real.
|
||||
- **The chain is unkeyed.** Each event folds in the previous digest, so an
|
||||
accidental edit or deletion is caught. An attacker with write access to the
|
||||
database can recompute the chain. For a real commitment, checkpoint the head
|
||||
digest somewhere the database cannot reach, or sign it.
|
||||
- **The service around this is unwritten.** TLS, CSRF, session transport, secret
|
||||
storage and the HTTP routes are not here. The library refuses to mint
|
||||
authority from a name; a route that forgets to pass a session still cannot,
|
||||
but that is only as good as the integration.
|
||||
- **Revocation has no expiry semantics.** Revoking works; scheduled or
|
||||
self-expiring revocation does not.
|
||||
- **No key recovery.** Rotate and you lose the old sessions. There is no escrow,
|
||||
which is a deliberate choice, not an oversight.
|
||||
|
||||
The tests assert the logic is correct. They do not make it safe.
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user