2 Commits
Author SHA1 Message Date
dsql 0d0558d11b chore: unpin envelope_crypto dependency (track latest release)
envauth used envelope_crypto pinned at v0.1.0, two data-loss fixes behind (the lib
is now v0.1.4). envauth uses only stable crypto primitives (initialize, encrypt_data/
decrypt_data, create_aes_key, *_aes_key_with_rsa, get_rsa_key_fingerprint, self_test)
— never the record-rotation functions whose contract changed — so tracking latest is
safe and keeps the crypto fixes flowing without a manual bump each release.

Verified: full CLI round-trip (init -> authorize server -> privilege gate -> envauth-1
self-authorize refusal -> list) against envelope_crypto v0.1.4 source; all 8 used
primitives present. v0.1.4.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-02 16:50:55 -04:00
dsql bfeee80712 fix: EA-1 refuse authorize of an already-recorded key (v0.1.3)
authorize never checked the target fingerprint against existing docs before
save()'s upsert-by-_id, so authorizing the local machine's own public key
under a new friendly name silently replaced the local authorizer record
(can_authorize demoted to False) while printing a success banner. With a
sole authorizer this bricks the CLI: authorize refuses (not permitted),
init refuses (already initialized), and revoke of the local key refuses
(refusing to revoke the local key) -- no in-CLI recovery. Mirror revoke's
local-key guard and extend it to any existing _id, so a duplicate target
is refused with a clear message instead of silently replacing the record.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-02 16:41:09 -04:00
4 changed files with 35 additions and 12 deletions
+9 -5
View File
@@ -13,26 +13,26 @@ authorization system and the key-document schema; the crypto primitives live in
## Install ## Install
``` ```
envelope_authorizer @ git+ssh://git@git.rethinkstudios.io/rethink-public/envelope_authorizer.git@v0.1.2 envelope_authorizer @ git+ssh://git@git.rethinkstudios.io/rethink-public/envelope_authorizer.git@v0.1.4
``` ```
Direct: Direct:
```bash ```bash
pip install "envelope_authorizer @ git+ssh://git@git.rethinkstudios.io/rethink-public/envelope_authorizer.git@v0.1.2" pip install "envelope_authorizer @ git+ssh://git@git.rethinkstudios.io/rethink-public/envelope_authorizer.git@v0.1.4"
``` ```
The base install uses a local JSON file for storage (stdlib only). For shared The base install uses a local JSON file for storage (stdlib only). For shared
dev→server storage, install the mongo extra: dev→server storage, install the mongo extra:
```bash ```bash
pip install "envelope_authorizer[mongo] @ git+ssh://git@git.rethinkstudios.io/rethink-public/envelope_authorizer.git@v0.1.2" pip install "envelope_authorizer[mongo] @ git+ssh://git@git.rethinkstudios.io/rethink-public/envelope_authorizer.git@v0.1.4"
``` ```
Installing pulls `envelope_crypto` (and `mongo` with the extra). After install, Installing pulls `envelope_crypto` (and `mongo` with the extra). After install,
the `authorizer` command is on your PATH; `python -m envelope_authorizer` also works. the `authorizer` command is on your PATH; `python -m envelope_authorizer` also works.
Drop the `@v0.1.2` suffix from the line above to install the latest unpinned. Drop the `@v0.1.4` suffix from the line above to install the latest unpinned.
## Trust model (read this) ## Trust model (read this)
@@ -129,7 +129,11 @@ initialized or the friendly name is taken.
Boots the local DEK, verifies the local key is itself an authorizer, then wraps 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 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 `--can-authorize` for servers (`allowed: False`); pass it only for trusted
dev/home machines. dev/home machines. Refuses a target key whose fingerprint already has a record
(most importantly the local key itself) — `save` upserts by `_id`, so
authorizing an already-known key would silently replace its existing doc
(capability flag and friendly name) under a success banner instead of adding a
new key. Revoke the existing record first if you intend to re-authorize it.
``` ```
[✔] Authorized Jy7k2ey7... | friendly: server1 [can_authorize=False] [✔] Authorized Jy7k2ey7... | friendly: server1 [can_authorize=False]
+2 -2
View File
@@ -4,11 +4,11 @@ build-backend = "hatchling.build"
[project] [project]
name = "envelope_authorizer" name = "envelope_authorizer"
version = "0.1.2" version = "0.1.4"
description = "CLI key-authorization manager for envelope_crypto" description = "CLI key-authorization manager for envelope_crypto"
requires-python = ">=3.10" requires-python = ">=3.10"
dependencies = [ dependencies = [
"envelope_crypto @ git+ssh://git@git.rethinkstudios.io/rethink-public/envelope_crypto.git@v0.1.0", "envelope_crypto @ git+ssh://git@git.rethinkstudios.io/rethink-public/envelope_crypto.git",
"tomli>=2.0; python_version<'3.11'", "tomli>=2.0; python_version<'3.11'",
] ]
+1 -1
View File
@@ -1 +1 @@
__version__ = "0.1.2" __version__ = "0.1.4"
+22 -3
View File
@@ -2,7 +2,11 @@
boots the local DEK, verifies the local key is itself an authorizer, then wraps 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. `--can-authorize` the same DEK to the target public key and stores a new key doc. `--can-authorize`
decides whether the new key may authorize others (omit it for servers). decides whether the new key may authorize others (omit it for servers). Refuses
a target key that fingerprints to an existing `_id` — `save` upserts by `_id`, so
authorizing a key that is already on record (most dangerously the local key
itself) would silently replace that doc's capability flag and friendly name
under a success banner instead of adding a new key.
""" """
from . import ( from . import (
@@ -10,6 +14,7 @@ from . import (
boot_local, boot_local,
build_doc, build_doc,
find_by_friendly, find_by_friendly,
local_fingerprint,
make_flag, make_flag,
read_flag, read_flag,
) )
@@ -25,9 +30,23 @@ def run(config, storage, args) -> None:
if not read_flag(crypto, local_doc["meta"]["authorizer"]): if not read_flag(crypto, local_doc["meta"]["authorizer"]):
raise CommandError("this key is not permitted to authorize others") raise CommandError("this key is not permitted to authorize others")
new_fp, new_wrapped = crypto.encrypt_aes_key_with_rsa( new_fp = crypto.get_rsa_key_fingerprint(args.key)
crypto.master_key, args.key if new_fp == local_fingerprint(crypto, config):
raise CommandError(
"target key is the local key; authorize would silently replace the "
"local authorizer record — use a different keypair, or `authorizer "
"list` if you meant to check its status"
) )
existing = storage.get(new_fp)
if existing:
existing_friendly = existing.get("meta", {}).get("friendly", "?")
raise CommandError(
f"target key is already authorized as '{existing_friendly}'; "
f"authorize would silently replace that record — revoke it first "
f"if you intend to re-authorize it"
)
_, new_wrapped = crypto.encrypt_aes_key_with_rsa(crypto.master_key, args.key)
flag = make_flag(crypto, args.can_authorize) flag = make_flag(crypto, args.can_authorize)
doc = build_doc(new_fp, new_wrapped, flag, config.identity, args.friendly) doc = build_doc(new_fp, new_wrapped, flag, config.identity, args.friendly)
storage.save(doc) storage.save(doc)