mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 03:25:20 +00:00
Authenticate private media capability per Noise session
This commit is contained in:
+85
-34
@@ -21,7 +21,7 @@ build that emitted it has expired and the project's minimum-supported-client
|
||||
policy excludes those builds. Track that release criterion explicitly; do not
|
||||
remove the alias on an arbitrary calendar date.
|
||||
|
||||
## Capability announcement
|
||||
## Discovery hint
|
||||
|
||||
Identity announcement TLV `0x05` is a minimal little-endian bitfield. Bit 8
|
||||
means the peer implements private-media v1, so its exact encoding is:
|
||||
@@ -35,41 +35,82 @@ means the peer implements private-media v1, so its exact encoding is:
|
||||
|
||||
Current Android builds include this TLV in broadcast and peer-directed
|
||||
announcements over both BLE and Wi-Fi Aware. Older clients safely skip the
|
||||
unknown TLV. Its absence, or a present TLV with bit 8 clear, describes a legacy
|
||||
client and does not invalidate the announcement.
|
||||
unknown TLV. Its absence, or a present TLV with bit 8 clear, does not invalidate
|
||||
the announcement.
|
||||
|
||||
Decoders retain unknown low-64-bit capability bits and unknown announcement
|
||||
TLVs so a decode/re-encode cycle does not erase newer extensions.
|
||||
|
||||
## Authenticated capability pinning
|
||||
The announcement bit is discovery metadata only. A self-signed announcement
|
||||
does not prove possession of its public Noise key, so it never authorizes
|
||||
encrypted media, creates a capability pin, or satisfies a pending send.
|
||||
|
||||
The capability bit is security-sensitive and is not trusted just because an
|
||||
announcement is self-signed. A client may promote bit 8 only after both of
|
||||
these checks succeed, in either arrival order:
|
||||
## Authenticated peer state (`0x21`)
|
||||
|
||||
1. The announcement has a valid Ed25519 signature using its advertised
|
||||
signing key.
|
||||
2. Its advertised Noise static key exactly matches the remote static key
|
||||
authenticated by the live Noise handshake.
|
||||
Every newly authenticated Noise generation, including a rekey with the same
|
||||
static key, exchanges a fresh peer-state proof. Its decrypted Noise payload
|
||||
type is `0x21`, followed by this canonical byte sequence:
|
||||
|
||||
After promotion, store an HSTS-style pin keyed by the SHA-256 fingerprint of
|
||||
that authenticated Noise static key. The pin is persistent, encrypted at rest,
|
||||
and cleared by panic wipe. Never derive it from a peer-ID cache,
|
||||
`PeerFingerprintManager`, an unverified announcement, or a Noise key that does
|
||||
not match the signed announcement.
|
||||
```text
|
||||
01 version
|
||||
01 <len 1...8> <value> capabilities, minimal little-endian
|
||||
02 20 <32 bytes> Ed25519 signing public key
|
||||
```
|
||||
|
||||
A pin prevents a later missing or cleared capability bit from downgrading the
|
||||
same authenticated identity. It never substitutes for a live authenticated
|
||||
Noise session when sending.
|
||||
Both known TLVs are required exactly once. Decoders reject an unknown version,
|
||||
truncated TLV, duplicate known TLV, a capability length outside `1...8`, a
|
||||
non-minimal capability value, or an Ed25519 key whose length is not 32. Unknown
|
||||
TLVs are skipped for forward compatibility, and the two known TLVs may arrive
|
||||
in either order.
|
||||
|
||||
Each endpoint sends `0x21` when the generation authenticates and echoes its
|
||||
local state at most once after accepting the peer's first valid proof for that
|
||||
generation. Repeated identical proofs are idempotent; a different second proof
|
||||
cannot replace the first within that generation. A five-second generation
|
||||
watchdog distinguishes an older client that ignores `0x21` from a new client
|
||||
that supplied a proof. Persisted state from a previous connection never
|
||||
satisfies the fresh-generation watchdog.
|
||||
|
||||
Locally, the Noise handshake hash is the generation token. Decryption returns
|
||||
that token with the plaintext, coordinator mutations hold a lease on that exact
|
||||
session, and private-media encryption accepts the policy decision only while
|
||||
the same token remains active. A same-static rekey therefore cannot reuse an
|
||||
older proof or race policy into encrypting on an unproved generation.
|
||||
|
||||
The proof is bound by the Noise channel to the exact authenticated 32-byte
|
||||
remote static key and canonical peer ID. Store its capabilities and 32-byte
|
||||
Ed25519 key under the SHA-256 fingerprint of that static key in encrypted
|
||||
identity state. A proof with bit 8 creates an HSTS-style private-media pin; a
|
||||
later no-bit proof does not erase that history. Panic wipe clears both records
|
||||
and prevents an in-flight pre-wipe controller from restoring them.
|
||||
|
||||
The persisted Ed25519 key is consulted before accepting later announcements.
|
||||
This lets a valid proof recover from a copied-static, wrong-Ed preannouncement,
|
||||
while preventing that preannouncement from winning again after restart. A
|
||||
fresh proof in a later Noise generation may intentionally rotate the Ed25519
|
||||
key; an announcement by itself cannot.
|
||||
|
||||
## Mixed-client send policy
|
||||
|
||||
- No live authenticated Noise remote-static key: emit no media or local echo,
|
||||
initiate one Noise handshake, and require the user to retry after it completes.
|
||||
- Authenticated and pinned, or authenticated with a matching verified bit-8
|
||||
announcement: send encrypted `0x11` / `0x20`.
|
||||
- Authenticated with a matching verified legacy announcement (TLV absent or bit
|
||||
clear): ask for explicit one-shot consent before sending this file.
|
||||
- No live authenticated Noise remote-static key: retain the first send intent,
|
||||
emit no media or local echo, and initiate one Noise handshake.
|
||||
- Live generation waiting for `0x21`: retain that same intent while the
|
||||
five-second peer-state watchdog runs.
|
||||
- Fresh proof with bit 8: automatically retry the retained intent and send
|
||||
encrypted `0x11` / `0x20` after final-packet admission.
|
||||
- Fresh proof without bit 8, or a five-second no-proof timeout for an unpinned
|
||||
old client: retry the retained intent by showing the existing explicit
|
||||
one-shot legacy consent prompt.
|
||||
- A previously pinned identity that proves no bit, or fails to prove state in
|
||||
the current generation, is visibly blocked. It cannot silently fall back.
|
||||
|
||||
The exact automatic intent is reserved before its first policy evaluation, so a
|
||||
proof or timeout callback racing that evaluation cannot be lost. It is bounded
|
||||
and singular, and expires after 15 seconds with a visible system message.
|
||||
Retries are serialized and always re-run current policy and exact packet
|
||||
admission. No waiting, rejected, expired, or cancelled attempt creates a local
|
||||
file echo or transmits raw media; terminal rejection is shown as a system
|
||||
message rather than failing silently.
|
||||
|
||||
The legacy consent path sends a recipient-directed raw
|
||||
`MessageType.FILE_TRANSFER` (`0x22`) packet. Its contents are visible to relays,
|
||||
@@ -79,9 +120,9 @@ failure aborts the send. Receivers reject unsigned or invalid signed directed
|
||||
raw files.
|
||||
|
||||
Consent is consumed at most once. On approval the sender re-runs the policy and
|
||||
packet admission checks. If the capability became authenticated while the
|
||||
dialog was open, the send upgrades to encrypted mode. Cancellation, duplicate
|
||||
approval, panic wipe, and changed security state cannot cause a later send.
|
||||
packet admission checks. If a bit-8 proof arrived while the dialog was open,
|
||||
the send upgrades to encrypted mode. Cancellation, duplicate approval, panic
|
||||
wipe, and changed security state cannot cause a later send.
|
||||
|
||||
## Final-packet admission
|
||||
|
||||
@@ -102,16 +143,26 @@ capability and bounded streaming design.
|
||||
|
||||
## Compatibility summary
|
||||
|
||||
- New Android to new iOS/Android: encrypted Noise `0x20` after authenticated
|
||||
capability promotion.
|
||||
- New Android to new iOS/Android: generation-scoped authenticated `0x21`
|
||||
exchange, then encrypted Noise `0x20`.
|
||||
- Prerelease iOS to new Android: encrypted Noise `0x09` is decoded,
|
||||
canonicalized to `0x20`, and delivered during the migration window.
|
||||
- New Android to a verified older client: blocked until the user explicitly
|
||||
accepts one relay-visible signed raw `0x22` transfer.
|
||||
- New Android to an older client: the unknown `0x21` is ignored; after the
|
||||
five-second watchdog, an unpinned identity may use one explicitly consented,
|
||||
relay-visible signed raw `0x22` transfer.
|
||||
- Old clients receiving a new announcement: ignore TLV `0x05` and continue
|
||||
operating normally.
|
||||
operating normally. Old clients receiving `0x21` drop the unknown inner type
|
||||
without affecting their existing Noise session or private messages.
|
||||
- A previously capable authenticated identity cannot force a silent downgrade
|
||||
by later omitting the bit.
|
||||
by omitting `0x21`, clearing the bit, or changing only its announcement.
|
||||
|
||||
Do not remove the `0x21` watchdog/legacy-consent migration path until the
|
||||
minimum-supported Android and iOS versions both emit an authenticated bit-8
|
||||
`0x21` proof on every Noise generation, and the released legacy population has
|
||||
aged out under the project's explicit support policy. Merely observing an
|
||||
announcement bit or waiting for an arbitrary date is not sufficient. The HSTS
|
||||
pin remains necessary after that point; removing old-client consent must not
|
||||
re-enable a raw automatic fallback.
|
||||
|
||||
Public media remains signed broadcast `MessageType.FILE_TRANSFER` (`0x22`) and
|
||||
is outside this private-media capability.
|
||||
|
||||
Reference in New Issue
Block a user