# envelope_authorizer A CLI key-authorization manager for [`envelope_crypto`](https://git.rethinkstudios.io/rethink-public/envelope_crypto). Installing it gives a project an `authorizer` command to manage **DEK access**: initialize the key system, authorize other machines, verify, list, and revoke. It is the clean extraction of the authorization half of an older monolith — the domain logic (vaults, services, API keys, wallet scan/sweep) is **out of scope** and stays in the projects that consume this lib. This lib owns only the authorization system and the key-document schema; the crypto primitives live in `envelope_crypto`. ## Install ``` envelope_authorizer @ git+ssh://git@git.rethinkstudios.io/rethink-public/envelope_authorizer.git@v0.1.0 ``` Direct: ```bash pip install "envelope_authorizer @ git+ssh://git@git.rethinkstudios.io/rethink-public/envelope_authorizer.git@v0.1.0" ``` The base install uses a local JSON file for storage (stdlib only). For shared dev→server storage, install the mongo extra: ```bash pip install "envelope_authorizer[mongo] @ git+ssh://git@git.rethinkstudios.io/rethink-public/envelope_authorizer.git@v0.1.0" ``` Installing pulls `envelope_crypto` (and `mongo` with the extra). After install, the `authorizer` command is on your PATH; `python -m envelope_authorizer` also works. ## Trust model (read this) There is one shared **AES data-encryption key (DEK)** per project. Each key doc stores that same DEK **RSA-wrapped to a different authorized public key**, so each machine unwraps it with its own RSA private key. **Private RSA keys are never stored** — only the wrapped DEK lives in the doc. Each key carries an **encrypted capability flag** — `meta.authorizer` = `encrypt_data({"allowed": bool})` — meaning "may this key authorize *other* keys?" - **Dev / home machines** are initialized or authorized with `allowed: True` → they can run `authorize` to grant new machines access. - **Servers** are authorized with `allowed: False` → they can boot and use the DEK (decrypt project data) but **cannot authorize anything else**. The flag is GCM-sealed under the DEK on purpose: a server **cannot flip its own flag in the database to escalate**, because forging a valid flag requires already holding the DEK, and editing the stored ciphertext breaks the auth tag. The guarantee is precisely: *you cannot grant yourself authority without already having it.* (A legitimate authorizer can of course mint new flags — that is its job. This stops passive DB tampering, not an authorizer.) `revoke` is **bookkeeping, not rotation**: it deletes the record so the key can no longer unwrap at boot, but it does not rotate the DEK or scrub it from a machine that already holds it. If a key is compromised, re-init the system and re-authorize trusted machines (DEK rotation lives in `envelope_crypto`). ## Quick start ```bash # 1. scaffold a config in the current project, then edit it authorizer config init # 2. on the DEV machine: create the key system (this key is an authorizer) authorizer init --friendly dev # 3. authorize a SERVER (no --can-authorize → it can decrypt but not authorize) authorizer authorize --key /path/to/server_pub.pem --friendly server1 # 4. see who's authorized authorizer list ``` The dev→server flow works across machines when both point at the same storage (the mongo backend). With the JSON backend they must share the file. ## Commands ### `authorizer config init` Writes a starter `.authorizer.toml` in the current directory (with a commented mongo section). Refuses to overwrite an existing file. Prints the path written. ### `authorizer init --friendly NAME` Creates a fresh DEK, wraps it to the local public key, and stores the first key doc as an **authorizer** (`allowed: True`). Refuses if the system is already initialized or the friendly name is taken. ``` [✔] Initialized — fingerprint: O3MBQfll... | friendly: dev [authorizer=True] ``` ### `authorizer authorize --key PATH --friendly NAME [--can-authorize]` Boots the local DEK, verifies the local key is itself an authorizer, then wraps the same DEK to the target public key and stores a new key doc. Omit `--can-authorize` for servers (`allowed: False`); pass it only for trusted dev/home machines. ``` [✔] Authorized Jy7k2ey7... | friendly: server1 [can_authorize=False] ``` A key with `allowed: False` that tries to authorize is refused: ``` [✘] this key is not permitted to authorize others ``` ### `authorizer verify` Boots the local DEK and runs `envelope_crypto`'s `self_test` (data round-trip + key wrap/unwrap) against the local keypair. Prints `[✔] Crypto system OK` or the failure. Never prints key material. ### `authorizer list` Prints every authorized key as a padded table (fingerprint, friendly, created_by, created_at, can-authorize). `CAN_AUTH` is decrypted from each doc's flag; a key the local machine can't unwrap shows `?`. Never prints the wrapped key or DEK. ``` FINGERPRINT FRIENDLY CREATED_BY CREATED_AT CAN_AUTH ------------------------------------------------------------------------------------- O3MBQflldInI6Vgt dev dsql@dev 2026-06-25 03:37:00 Yes Jy7k2ey7KfQl0w5V server1 dsql@dev 2026-06-25 03:37:05 No 2 key(s) authorized ``` ### `authorizer revoke --friendly NAME` | `--fingerprint FP` Finds the key by friendly name or fingerprint prefix, refuses to revoke the local key, deletes the record, and prints the honesty warning that revoke is not rotation. ## Config reference TOML, searched cwd-first then `~`: `.authorizer.toml`. Paths expand `~`. No defaults are baked in — a missing required field raises an error naming the field and the config path. ### JSON backend (default, stdlib only) ```toml [keys] public = "~/.ssh/id_rsa.pub" private = "~/.ssh/id_rsa" identity = "user@hostname" # stamped as created_by on every key doc [storage] backend = "json" path = ".authorizer_keys.json" ``` ### Mongo backend (`envelope_authorizer[mongo]`) Shared storage so dev authorizes a server across machines. Writes are upserts on `_id`. ```toml [keys] public = "~/.ssh/id_rsa.pub" private = "~/.ssh/id_rsa" identity = "user@hostname" [storage] backend = "mongo" uri = "mongodb://localhost:27017" database = "myapp" collection = "keys" ``` ## Key document schema Owned by this lib (not `envelope_crypto`): ```python { "_id": fingerprint, # SHA-256 fingerprint of the public key "key": wrapped_b64, # the AES DEK, RSA-wrapped to THIS pubkey "meta": { "authorizer": {"secure": True, "iv": "...", "data": "..."}, # encrypted flag "created_by": "dsql@dev", "created_at": 1782358620, "friendly": "dev", }, } ``` ## Versioning Tagged `vX.Y.Z`. Pin the tag. `envelope_crypto` is pinned at `v0.1.0` in `pyproject.toml`; to change it, edit the pin and re-test.