Audit LOW: empty offer, attachment id, QR binding, skipped-key order
Same empty-offer and attachment-id rules as iOS. The QR scanner now binds a scanned code by its fnid rather than the handle even on the home relay — the fnid certifies itself, a handle is a name the relay maps, so resolving by handle handed the relay the choice of who you scanned. And a skipped message key is consumed only after its tag verifies: it was defused today by the caller discarding the session, but any future in-memory ratchet cache would have turned it into real message loss. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
cbe2292d3c
commit
130edd7019
@@ -201,10 +201,23 @@ class DoubleRatchet private constructor(
|
||||
val aad = associatedData + message.header.canonicalJson()
|
||||
|
||||
// A key held for a message that never arrived, or arrived late.
|
||||
//
|
||||
// 🔴 **Removed only AFTER the tag verifies (audit LOW).** Consuming it
|
||||
// first meant a forged ciphertext for a stashed (dh, n) burned the key
|
||||
// the real delayed message needed — the genuine message could then
|
||||
// never be opened. Today the caller discards the whole session object
|
||||
// on a failed decrypt, so the loss never reaches the vault; that is a
|
||||
// property of the CALLER, and the moment anything caches a ratchet in
|
||||
// memory it would become a real message-loss bug. iOS has always
|
||||
// removed after the open for exactly this reason.
|
||||
val skippedKey = SkippedKey(Bytes.b64(message.header.dh), message.header.n)
|
||||
skipped.remove(skippedKey)?.let { mk ->
|
||||
skipped[skippedKey]?.let { mk ->
|
||||
val (aesKey, nonce) = aeadParams(mk)
|
||||
return Aead.open(aesKey, nonce + message.ciphertext, aad)
|
||||
val plaintext = Aead.open(aesKey, nonce + message.ciphertext, aad)
|
||||
// LinkedHashMap keeps insertion order, so removing from it is the
|
||||
// whole eviction bookkeeping — there is no separate order list.
|
||||
skipped.remove(skippedKey)
|
||||
return plaintext
|
||||
}
|
||||
|
||||
// A new ratchet key from the far side: catch up on their old chain,
|
||||
|
||||
Reference in New Issue
Block a user