fix: reencrypt/is_encrypted_record/decrypt_record miss self-blob and deep-nested records
Two silent-data-loss paths in the record-level functions: (1) reencrypt's
traversal_level cutoff silently left blobs nested deeper than the default
under the old key with no signal, contradicting its own documented fail-loud
rotation contract, and is_encrypted_record shared the cutoff so a
post-rotation audit couldn't detect the leftover; (2) all three record
functions inspected only record.values(), never the record itself, so a bare
{secure, iv, data} blob used as the whole document (the README's file-storage
pattern) was invisible to is_encrypted_record and passed through reencrypt
unchanged under the old key.
reencrypt now raises when a blob sits deeper than traversal_level instead of
silently truncating, and detects/handles the record-itself-is-a-blob case;
is_encrypted_record falls back to an unbounded-depth scan past
traversal_level so it reliably flags leftovers regardless of nesting depth;
decrypt_record likewise handles a record that is itself a blob. Bumped to
v0.1.4.
Signed-off-by: disqualifier <dev@disqualifier.me>
This commit is contained in:
@@ -11,18 +11,18 @@ and storage-agnostic.
|
||||
`requirements.txt`:
|
||||
|
||||
```
|
||||
envelope_crypto @ git+ssh://git@git.rethinkstudios.io/rethink-public/envelope_crypto.git@v0.1.3
|
||||
envelope_crypto @ git+ssh://git@git.rethinkstudios.io/rethink-public/envelope_crypto.git@v0.1.4
|
||||
```
|
||||
|
||||
Direct:
|
||||
|
||||
```bash
|
||||
pip install "envelope_crypto @ git+ssh://git@git.rethinkstudios.io/rethink-public/envelope_crypto.git@v0.1.3"
|
||||
pip install "envelope_crypto @ git+ssh://git@git.rethinkstudios.io/rethink-public/envelope_crypto.git@v0.1.4"
|
||||
```
|
||||
|
||||
Requires `cryptography` (pulled transitively).
|
||||
|
||||
Drop the `@v0.1.3` 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.
|
||||
|
||||
## First-time setup
|
||||
|
||||
@@ -75,7 +75,9 @@ plain = crypto.decrypt_data(enc) # -> {"ssn": "..."}
|
||||
|
||||
For whole records: `decrypt_record(crypto, doc)` decrypts every `{secure, iv, data}`
|
||||
field (nested up to `traversal_level`, default 2); `is_encrypted_record(doc)` reports
|
||||
whether any encrypted field exists.
|
||||
whether any encrypted field exists. Both also detect `doc` itself being a bare
|
||||
`{secure, iv, data}` blob (the file-storage pattern below, where the blob IS the whole
|
||||
document) — not just blobs nested under a key.
|
||||
|
||||
```python
|
||||
from envelope_crypto import is_encrypted_record, decrypt_record
|
||||
@@ -84,6 +86,11 @@ if is_encrypted_record(doc):
|
||||
doc = decrypt_record(crypto, doc)
|
||||
```
|
||||
|
||||
`is_encrypted_record` falls back to an unbounded-depth scan once `traversal_level` is
|
||||
exhausted, so it reliably reports `True` for a blob left behind by a shallower
|
||||
`decrypt_record`/`reencrypt` call — safe to use as a leftover-detecting audit after
|
||||
rotation, regardless of how deep the blob is nested.
|
||||
|
||||
Naming aliases (same objects): `EnvelopeCrypto` = `DocumentCrypto` = `RecordCrypto`
|
||||
= `PCICrypto` (deprecated legacy alias). `decrypt_record` = `decrypt_document` =
|
||||
`decrypt_dict`; `is_encrypted_record` = `is_encrypted_document` = `is_encrypted_dict`.
|
||||
@@ -129,7 +136,14 @@ for fingerprint, wrapped_key in wrapped.items():
|
||||
|
||||
`reencrypt(source_crypto, record)` is a method on the **destination** (new-key)
|
||||
instance: it decrypts each encrypted field with `source_crypto` (old key) and
|
||||
re-encrypts with itself. Only `{secure, ...}` fields are touched.
|
||||
re-encrypts with itself. Only `{secure, ...}` fields are touched — including `record`
|
||||
itself if it IS a `{secure, iv, data}` blob (the file-storage pattern).
|
||||
|
||||
Rotation must fail loud: a per-field decrypt failure raises, and so does a blob nested
|
||||
deeper than `traversal_level` — silently leaving it under the old key would strand it
|
||||
once the old key's wrapped-key record is deleted below. If you nest blobs deeper than
|
||||
the default `traversal_level=2`, pass a higher `traversal_level` or flatten the record
|
||||
first.
|
||||
|
||||
## Storage patterns
|
||||
|
||||
|
||||
Reference in New Issue
Block a user