docs: revise keyupdate draft after link2xt review

review comment: https://github.com/chatmail/core/pull/8595#discussion_r3791274942

The intended recipient fingerprint subpacket is optional,
so drop the contact-list-leak argument against per-recipient chunking
and add a section on why keyupdates are signed even if they don't need to be.
This commit is contained in:
holger krekel
2026-08-16 16:22:13 +02:00
parent 4116d7579b
commit 85fd53add2
+57 -27
View File
@@ -101,11 +101,8 @@ so the channel would let us drop the concept completely.
We should be able to hand our current key to our contacts We should be able to hand our current key to our contacts
without waiting for a conversation to happen. without waiting for a conversation to happen.
One way to address such a message is per recipient, The straightforward way is to address such a message per recipient,
in chunks of about twenty contacts, as sketched in [#8588]. in chunks of a few dozen contacts, as sketched in [#8588].
To name two problems: it reveals a slice of your contact list to each contact,
because the signature names every intended recipient in the chunk,
and it uploads the keyupdate message 10 times for 200 contacts.
This draft argues for a different way of sending key updates, This draft argues for a different way of sending key updates,
starting with a simple observation: starting with a simple observation:
@@ -127,17 +124,19 @@ What using automatically derived secrets and existing broadcast encryption buys:
- No new header names, no new key distribution or bookkeeping protocol (phew!), - No new header names, no new key distribution or bookkeeping protocol (phew!),
only a new `Chat-Content` value. only a new `Chat-Content` value.
- Network cost is largely independent of contact count for senders. - Network cost is largely independent of recipient count.
One message per relay change rather than one per chunk. A keyupdate is a few KB, rendered once per relay change,
Relays by default currently allow 1000 recipients per submission, and addresses only travel as `RCPT TO` commands in the SMTP transaction.
so a keyupdate (which is below 10Kbyte even with Autocrypt2), Chatmail relays take 1000 addresses per submission,
would be uploaded once for anyone below 1K contacts, so for most profiles today a keyupdate is a single upload,
which today is the vast majority by a wide margin. and each upload stays well under 100KB on the wire.
- Recipients learn nothing about our contact list. - Recipients learn nothing about our contact list
Every recipient gets the same encrypted message with no per-recipient framing, because nothing in the message is recipient-specific:
and no chunk has to group contacts together. one password-encrypted session key packet instead of one per recipient,
The submitting relay still sees the envelope, as it does today. and a signature that names only its issuer,
with no `intended recipient fingerprint` subpackets.
The submitting relay still sees the envelope metadata, as it does today.
- Contacts acquired at any point in the past are reached. - Contacts acquired at any point in the past are reached.
The secret comes from the key itself and not from a shared session, The secret comes from the key itself and not from a shared session,
@@ -154,14 +153,14 @@ so "Remove" can mean actually removed, like users intend it.
It would also unblock automatic relay management. It would also unblock automatic relay management.
What ships under that name today is initial onboarding only What ships under that name today is initial onboarding only
([#8444], still off by default): no rotation, no removal. ([#8444], still off by default): no rotation, no removal.
Designing those is hard while changing a relay is unsafe, Designing those proved to be hard while changing a relay is unsafe,
because any automatic change would silently cut off because any automatic change would silently cut off
the contacts who do not hear about it. the contacts who do not hear about it.
Keyupdates would lift that constraint and could land in the next release, Keyupdates would lift that constraint and could land in the next release,
well before automatic addition/removal mechanics are settled. well before automatic addition/removal mechanics are settled.
## Keyupdates are decryptable forever, MUST only contain key updates ## Keyupdates are decryptable forever, so MUST only contain key updates
Anyone who ever obtained our "public" key could derive the secret Anyone who ever obtained our "public" key could derive the secret
and decrypt these messages, forever. and decrypt these messages, forever.
@@ -280,14 +279,13 @@ Properties that follow, and requirements they imply:
- **Only update the key the secret was derived from.** - **Only update the key the secret was derived from.**
A keyupdate must be signed by that key and carry an update to it, A keyupdate must be signed by that key and carry an update to it,
and anything else is dropped. and anything else is dropped.
The rule is needed because the AEAD tag only proves the writer knew the Without the rule anyone holding a contact's key could send
secret, which every key holder does, so it authenticates nobody by itself.
Certificate merging already prevents a forged relay list,
but without the rule anyone holding a contact's key could send
a stream of messages carrying freshly generated keys in the Autocrypt a stream of messages carrying freshly generated keys in the Autocrypt
header, each silently creating a contact and a stored key header, each silently creating a contact the user never sees,
the user never sees, because keyupdates are trashed. because keyupdates are trashed.
Note that keyupdates can only arrive for contacts whose key we already have. The signature is not what makes the key credible, though,
and it is not what limits the audience either,
see *Keyupdates could come from anywhere but are signed anyway* below.
- **Domain-separated.** - **Domain-separated.**
A v6 fingerprint is also a SHA-256 over the same key material, A v6 fingerprint is also a SHA-256 over the same key material,
@@ -320,9 +318,8 @@ Properties that follow, and requirements they imply:
which any sender could trigger with undecryptable garbage. which any sender could trigger with undecryptable garbage.
Keyupdate secrets should therefore be tried last, Keyupdate secrets should therefore be tried last,
after the securejoin and broadcast secrets. after the securejoin and broadcast secrets.
Parsing and deriving on-demand in the try-decryption pipeline Parsing and deriving on demand was measured at under a second
is probably fine even for thousands of contacts, for a thousand contacts, so the secrets need no caching layer.
at tens of microseconds per certificate.
- **One body, many deliveries.** - **One body, many deliveries.**
The body would be rendered once and independently from the contact count. The body would be rendered once and independently from the contact count.
@@ -355,6 +352,39 @@ Properties that follow, and requirements they imply:
and because we perform some merging on concurrent transport additions. and because we perform some merging on concurrent transport additions.
### Keyupdates could come from anywhere but are signed anyway
The relay list in a keyupdate lives in a direct key self-signature,
and certificate merging verifies it and prefers the newest one,
so the resulting certificate is as trustworthy whether the keyupdate
arrived signed or unsigned, from the key owner or from a stranger.
Merging, not signing, is the cryptographic gate for every certificate we get,
so conceptually we could accept keyupdates from anyone.
However, the secret is derived from the key,
so we only ever ingest keyupdates from contacts whose certificate we store,
an audience fixed by construction rather than by policy.
Keyupdates should be signed
for implementation simplicity and a smaller attack surface:
every past holder of the key can derive the secret,
so the AEAD tag says only that the writer was one of them
while the signature narrows it to the key owner,
and core takes the sender contact from it,
treating a message without one as unencrypted and discarding it.
Unsigned keyupdates stay conceptually defensible,
but they would need an exception from the rule
that a contact-bound secret implies exactly one signature by that contact,
which broadcast channels and securejoin rest on too, and that is not worth it.
The same reasoning bounds a possible extension:
If we let our key-contacts gossip us other keys,
a keyupdate can carry `Autocrypt-Gossip` headers,
constrained to contacts we already share so no unknown one is added.
Conceptually that is fine but requires more implementation changes,
including the sender side of gossiping keyupdates,
so it is best considered separately from a first keyupdate release.
## Out of scope: envelope SMTP failures can terminate all sending ## Out of scope: envelope SMTP failures can terminate all sending
A permanently refused `RCPT TO` would fail the whole SMTP transaction, A permanently refused `RCPT TO` would fail the whole SMTP transaction,