summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-common/src/meshbay_common
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-09 05:31:05 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-09 05:31:05 +0200
commit3b2dd318477eb268e6821fb000aeadfe60d85987 (patch)
tree0c46c3ea663490bbee847a0eec5a41c41ad6561b /packages/meshbay-common/src/meshbay_common
parent6d64da7816aec2cc58bb1a6f0aceb9c9a921129f (diff)
downloadmeshbay-3b2dd318477eb268e6821fb000aeadfe60d85987.tar.gz
feat: Phase 6 complete — chat, multi-group, federation, replication, webcrypto
6.1 Double Ratchet (meshbay_common/ratchet.py): Forward secrecy, break-in recovery, out-of-order delivery. Signal-spec KDF_RK/KDF_CK via HKDF-SHA256. 11/11 tests. 6.2 Multi-group node (config.py): [[groups]] TOML array, per-group ports, back-compat [group]. 6.3 MHP federation persistence (db/models.py FederatedGroup + SwarmSource): receive_directory() now persists to federated_groups table. list_public_groups() includes federated results with source attribution. 6.4 Content replication (node/replication.py + hub SwarmSource): ContentReplicator: fetch-index, download, hash-verify, register-swarm. Hub: POST /v1/swarm/register, GET /v1/swarm/{hash} for multi-source. 6.5 Browser private group (webcrypto.py + static/crypto.js): AES-256-GCM variant of GEK for WebCrypto-compatible groups. crypto.js: SubtleCrypto importGEK + deriveChunkKey + decryptChunk. Keys distinct from ChaCha20 via :aes HKDF info suffix. 4/4 tests. 74/74 tests total. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-common/src/meshbay_common')
-rw-r--r--packages/meshbay-common/src/meshbay_common/webcrypto.py51
1 files changed, 51 insertions, 0 deletions
diff --git a/packages/meshbay-common/src/meshbay_common/webcrypto.py b/packages/meshbay-common/src/meshbay_common/webcrypto.py
new file mode 100644
index 0000000..58958f8
--- /dev/null
+++ b/packages/meshbay-common/src/meshbay_common/webcrypto.py
@@ -0,0 +1,51 @@
+"""
+MeshBay — AES-256-GCM cipher variant for browser-accessible groups.
+
+The ChaCha20-Poly1305 GEK used in MNP (TCP+TLS and QUIC transport)
+is NOT available in the WebCrypto API. For groups whose content must
+be decryptable by a web browser (using SubtleCrypto), an AES-256-GCM
+variant is used instead.
+
+The GEK wrapping (X25519 + HKDF) is identical — only the content
+cipher changes. The hub stores and distributes GEK bundles the same way.
+
+Cipher selection is declared per-group in the hub registry:
+ "cipher": "chacha20-poly1305" (default, native clients)
+ "cipher": "aes-256-gcm" (browser-compatible groups)
+
+Python side (this module):
+ encrypt_chunk_aes / decrypt_chunk_aes
+
+JavaScript side (in static/crypto.js):
+ Uses SubtleCrypto.importKey + SubtleCrypto.decrypt with AES-GCM.
+
+Key derivation for AES variant — same HKDF info string with suffix:
+ info = b"file:" + file_hash + b":chunk:" + chunk_index + b":aes"
+
+This ensures AES and ChaCha20 keys are always distinct even from the same GEK.
+"""
+
+import os
+from cryptography.hazmat.primitives.ciphers.aead import AESGCM
+from cryptography.hazmat.primitives.kdf.hkdf import HKDF
+from cryptography.hazmat.primitives import hashes
+
+
+def chunk_key_aes(gek: bytes, file_hash: bytes, chunk_index: int) -> bytes:
+ """Derive a per-chunk AES-256 key. Distinct from ChaCha20 key."""
+ return HKDF(
+ algorithm=hashes.SHA256(), length=32, salt=None,
+ info=b"file:" + file_hash + b":chunk:" + chunk_index.to_bytes(4, "big") + b":aes",
+ ).derive(gek)
+
+
+def encrypt_chunk_aes(key: bytes, plaintext: bytes) -> tuple[bytes, bytes]:
+ """Encrypt with AES-256-GCM. Returns (nonce, ciphertext+tag)."""
+ nonce = os.urandom(12) # 96-bit nonce (WebCrypto standard)
+ ct = AESGCM(key).encrypt(nonce, plaintext, None)
+ return nonce, ct
+
+
+def decrypt_chunk_aes(key: bytes, nonce: bytes, ciphertext: bytes) -> bytes:
+ """Decrypt with AES-256-GCM. Raises InvalidTag on failure."""
+ return AESGCM(key).decrypt(nonce, ciphertext, None)