aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-common/src/meshbay_common/groupbox.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-common/src/meshbay_common/groupbox.py')
-rw-r--r--packages/meshbay-common/src/meshbay_common/groupbox.py17
1 files changed, 17 insertions, 0 deletions
diff --git a/packages/meshbay-common/src/meshbay_common/groupbox.py b/packages/meshbay-common/src/meshbay_common/groupbox.py
index f1091c3..eb2f630 100644
--- a/packages/meshbay-common/src/meshbay_common/groupbox.py
+++ b/packages/meshbay-common/src/meshbay_common/groupbox.py
@@ -24,6 +24,14 @@ and group configuration. It buys nothing against a network observer (DTLS/TLS
already covers that), nothing against the hub (it never sees channel traffic), and
nothing against a member — who holds the GEK. That is the whole claim.
+**Both, for the upload (MNP 2.0).** `file_upload` carried the filename and the raw
+bytes in clear, and `file_upload_ack` carried the name it was stored under. The
+download path had been sealed end to end since the beginning — so the same file was
+ciphertext coming out of a node and plaintext going in, which is not a threat model,
+it is an oversight. The node holds the GEK for its own group, so unlike the index
+this direction seals *towards* the node: it opens the payload before it writes
+anything to disk, and refuses a chunk that does not open rather than guessing.
+
Purpose separation is deliberate. `GroupIndex.serialize()` reuses
`chunk_key_aes(gek, file_hash, chunk_index)` with a pseudo-file ("the index as chunk
0 of a virtual index file"), which borrows a file's key space for something that is
@@ -41,6 +49,7 @@ from cryptography.hazmat.primitives.kdf.hkdf import HKDF
PURPOSE_INDEX = "index"
PURPOSE_ACK = "ack"
+PURPOSE_UPLOAD = "upload"
# `salt=None` here and `salt: new Uint8Array(0)` in crypto.js agree — RFC 5869
# extracts with a zero key either way. Already proven in production by
@@ -48,8 +57,16 @@ PURPOSE_ACK = "ack"
_INFO = {
PURPOSE_INDEX: b"meshbay:index:v1",
PURPOSE_ACK: b"meshbay:ack:v1",
+ PURPOSE_UPLOAD: b"meshbay:upload:v1",
}
+# One subkey per purpose, and `seal` draws a fresh 96-bit nonce per message, so
+# the bound that matters is birthday collision under `PURPOSE_UPLOAD` — the only
+# purpose with real volume, one message per 48 KiB chunk. 2**32 chunks is 200 TB
+# uploaded under a single GEK before the collision probability reaches 2**-32,
+# and `gek_rotate` exists. Deriving the nonce from the payload instead would be
+# worse, not better: two chunks of identical bytes are ordinary in a file.
+
NONCE_LEN = 12 # 96-bit, the WebCrypto AES-GCM standard