starfish-keyring 3.0.0a8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- starfish_keyring-3.0.0a8/PKG-INFO +12 -0
- starfish_keyring-3.0.0a8/README.md +40 -0
- starfish_keyring-3.0.0a8/pyproject.toml +29 -0
- starfish_keyring-3.0.0a8/setup.cfg +4 -0
- starfish_keyring-3.0.0a8/starfish_keyring/__init__.py +60 -0
- starfish_keyring-3.0.0a8/starfish_keyring/_crypto_helpers.py +27 -0
- starfish_keyring-3.0.0a8/starfish_keyring/keyring.py +733 -0
- starfish_keyring-3.0.0a8/starfish_keyring/recipients.py +355 -0
- starfish_keyring-3.0.0a8/starfish_keyring.egg-info/PKG-INFO +12 -0
- starfish_keyring-3.0.0a8/starfish_keyring.egg-info/SOURCES.txt +14 -0
- starfish_keyring-3.0.0a8/starfish_keyring.egg-info/dependency_links.txt +1 -0
- starfish_keyring-3.0.0a8/starfish_keyring.egg-info/requires.txt +8 -0
- starfish_keyring-3.0.0a8/starfish_keyring.egg-info/top_level.txt +1 -0
- starfish_keyring-3.0.0a8/tests/test_keyring.py +635 -0
- starfish_keyring-3.0.0a8/tests/test_keyring_secp256k1.py +245 -0
- starfish_keyring-3.0.0a8/tests/test_recipients.py +568 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: starfish-keyring
|
|
3
|
+
Version: 3.0.0a8
|
|
4
|
+
Summary: Starfish multi-recipient encryption layer (keyring documents, recipient management, AES-GCM payload encryption)
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Requires-Dist: cryptography>=41.0
|
|
7
|
+
Requires-Dist: starfish-protocol
|
|
8
|
+
Requires-Dist: starfish-sdk
|
|
9
|
+
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
11
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
|
|
12
|
+
Requires-Dist: respx>=0.23.1; extra == "dev"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# starfish-keyring
|
|
2
|
+
|
|
3
|
+
Starfish multi-recipient encryption layer for Python — keyring documents, recipient management, AES-GCM payload encryption.
|
|
4
|
+
|
|
5
|
+
This is the encryption extension for Starfish v3. It is independent of `starfish-sdk` transport in the sense that the core `StarfishClient` does not import it; apps that want client-side payload encryption install this package and wire it on top.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
pip install starfish-sdk starfish-keyring
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from starfish_sdk import StarfishClient
|
|
17
|
+
from starfish_keyring import (
|
|
18
|
+
create_keyring,
|
|
19
|
+
create_keyring_encryptor,
|
|
20
|
+
add_collection_recipient,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
keyring, cek = create_keyring(adder_ed_priv, adder_ed_pub, [alice_kem_pub])
|
|
24
|
+
encryptor = create_keyring_encryptor(
|
|
25
|
+
keyring,
|
|
26
|
+
alice_kem_pub,
|
|
27
|
+
alice_kem_priv,
|
|
28
|
+
trusted_adders=[adder_ed_pub], # required — Ed25519 pubkey(s) you trust to grant access
|
|
29
|
+
)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`trusted_adders` is **required** (raises without it): the per-entry `addedSig` is self-attesting, so a provenance pin is needed to reject a server-substituted wrap entry. Pass the keyword-only `min_epoch` (the highest `current_epoch` you've previously seen, persisted client-side) to reject a stale keyring a hostile server might serve to undo a rotation. The recipient-management helpers (`add_collection_recipient`, `remove_recipient`, `list_recipients`) require the same `trusted_adders` pin.
|
|
33
|
+
|
|
34
|
+
The encryptor exposes `encrypt`/`decrypt` for JSON payloads and `seal_bytes(data, aad=None)` / `open_bytes(blob, aad=None)` for raw binary attachments (stored via the client's `push_blob` in an `encryption: "none"` binary collection). A sealed blob is `[u32 BE epoch][12-byte IV][AES-256-GCM ciphertext‖tag]`; the epoch and the caller's `aad` (typically the blob's storage path) are bound into the GCM tag, so a hostile server cannot relocate the blob to another path or replay it at a different epoch. This is byte-compatible with the TypeScript keyring's `sealBytes`/`openBytes`, so a blob sealed in one language opens in the other.
|
|
35
|
+
|
|
36
|
+
## Epochs & rotation
|
|
37
|
+
|
|
38
|
+
Revoking a recipient (`remove_recipient`) **rotates** the keyring to a new epoch and re-wraps the CEK for everyone who remains, so the revoked party can't read new content. `add_collection_recipient` wraps a newcomer into the **current epoch only** — content sealed under an earlier epoch stays unreadable to them (you'll see `No key available for epoch N` on decrypt). To share existing content with a freshly-added recipient, **re-seal it at the current epoch** (decrypt with a key that holds the old CEK, then re-encrypt) after adding them.
|
|
39
|
+
|
|
40
|
+
See `docs/python/keyring/` (and the TypeScript counterpart in `docs/ts/keyring/`) for the full guide.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "starfish-keyring"
|
|
7
|
+
version = "3.0.0a8"
|
|
8
|
+
description = "Starfish multi-recipient encryption layer (keyring documents, recipient management, AES-GCM payload encryption)"
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"cryptography>=41.0",
|
|
12
|
+
"starfish-protocol",
|
|
13
|
+
"starfish-sdk",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[project.optional-dependencies]
|
|
17
|
+
dev = [
|
|
18
|
+
"pytest>=7.0",
|
|
19
|
+
"pytest-asyncio>=0.21",
|
|
20
|
+
"respx>=0.23.1",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[tool.uv.sources]
|
|
24
|
+
starfish-protocol = { path = "../protocol", editable = true }
|
|
25
|
+
starfish-sdk = { path = "../client", editable = true }
|
|
26
|
+
|
|
27
|
+
[tool.pytest.ini_options]
|
|
28
|
+
asyncio_mode = "auto"
|
|
29
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""``starfish-keyring`` — multi-recipient encryption layer.
|
|
2
|
+
|
|
3
|
+
Public surface: keyring lifecycle (create/add/rotate), encryptor factory,
|
|
4
|
+
wrap/unwrap primitives, recipient management bound to a Starfish collection,
|
|
5
|
+
and the locked protocol constants.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from starfish_keyring.keyring import (
|
|
9
|
+
KEYRING_IV_BYTES,
|
|
10
|
+
KEYRING_WRAP_INFO,
|
|
11
|
+
KEYRING_WRAP_SALT,
|
|
12
|
+
Keyring,
|
|
13
|
+
KeyringEncryptor,
|
|
14
|
+
KeyringEpoch,
|
|
15
|
+
WrappedKeyEntry,
|
|
16
|
+
add_recipient,
|
|
17
|
+
create_keyring,
|
|
18
|
+
create_keyring_encryptor,
|
|
19
|
+
rotate_epoch,
|
|
20
|
+
unwrap_from_entry,
|
|
21
|
+
verify_entry_signature,
|
|
22
|
+
wrap_for_recipient,
|
|
23
|
+
)
|
|
24
|
+
from starfish_keyring.recipients import (
|
|
25
|
+
AdderKeys,
|
|
26
|
+
ListedRecipient,
|
|
27
|
+
RecipientRef,
|
|
28
|
+
add_recipient as add_collection_recipient,
|
|
29
|
+
current_epoch,
|
|
30
|
+
keyring_path_for,
|
|
31
|
+
list_recipients,
|
|
32
|
+
remove_recipient,
|
|
33
|
+
)
|
|
34
|
+
from starfish_keyring._crypto_helpers import hkdf_bytes
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"hkdf_bytes",
|
|
38
|
+
"KEYRING_IV_BYTES",
|
|
39
|
+
"KEYRING_WRAP_INFO",
|
|
40
|
+
"KEYRING_WRAP_SALT",
|
|
41
|
+
"Keyring",
|
|
42
|
+
"KeyringEncryptor",
|
|
43
|
+
"KeyringEpoch",
|
|
44
|
+
"WrappedKeyEntry",
|
|
45
|
+
"add_recipient",
|
|
46
|
+
"create_keyring",
|
|
47
|
+
"create_keyring_encryptor",
|
|
48
|
+
"rotate_epoch",
|
|
49
|
+
"unwrap_from_entry",
|
|
50
|
+
"verify_entry_signature",
|
|
51
|
+
"wrap_for_recipient",
|
|
52
|
+
"AdderKeys",
|
|
53
|
+
"ListedRecipient",
|
|
54
|
+
"RecipientRef",
|
|
55
|
+
"add_collection_recipient",
|
|
56
|
+
"current_epoch",
|
|
57
|
+
"keyring_path_for",
|
|
58
|
+
"list_recipients",
|
|
59
|
+
"remove_recipient",
|
|
60
|
+
]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Internal crypto helpers shared by identity, keyring, and pairing.
|
|
2
|
+
|
|
3
|
+
The leading underscore on the module name marks this as module-private — it
|
|
4
|
+
is NOT re-exported from ``__init__.py``. External consumers should not
|
|
5
|
+
import it.
|
|
6
|
+
|
|
7
|
+
This helper was previously duplicated verbatim across three modules
|
|
8
|
+
(``identity.py``, ``keyring.py``, ``pairing.py``); consolidating it keeps
|
|
9
|
+
the byte-level behavior identical while removing copy-paste drift risk.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from cryptography.hazmat.primitives import hashes
|
|
13
|
+
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def hkdf_bytes(ikm: bytes, salt: bytes, info: bytes, length: int = 32) -> bytes:
|
|
17
|
+
"""HKDF-SHA256 → ``length`` raw bytes.
|
|
18
|
+
|
|
19
|
+
Mirrors the TypeScript ``hkdfBytes`` (Web Crypto ``deriveBits``) so
|
|
20
|
+
cross-language test vectors remain byte-identical.
|
|
21
|
+
"""
|
|
22
|
+
return HKDF(
|
|
23
|
+
algorithm=hashes.SHA256(),
|
|
24
|
+
length=length,
|
|
25
|
+
salt=salt,
|
|
26
|
+
info=info,
|
|
27
|
+
).derive(ikm)
|