CLI key-authorization manager for envelope_crypto
Go to file
2026-06-25 00:04:40 -04:00
src/envelope_authorizer add package: pyproject + src (authorizer CLI, json/mongo storage, gated capability flag) 2026-06-25 00:04:40 -04:00
.gitignore init: CLI key-authorization manager for envelope_crypto 2026-06-25 00:04:37 -04:00
pyproject.toml add package: pyproject + src (authorizer CLI, json/mongo storage, gated capability flag) 2026-06-25 00:04:40 -04:00
README.md init: CLI key-authorization manager for envelope_crypto 2026-06-25 00:04:37 -04:00

envelope_authorizer

A CLI key-authorization manager for 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:

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:

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 flagmeta.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

# 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)

[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.

[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):

{
    "_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.