236ebfe4d6
MlsGroup.encrypt / decrypt used to pass raw application bytes as the
AEAD plaintext — no PrivateMessageContent framing, no FramedContentTBS
signature, no padding. Amethyst ↔ Amethyst round-trips worked (both
sides skipped it the same way) but every cross-implementation message
was unreadable.
Per RFC 9420 §6.3.1 the AEAD plaintext for an application PrivateMessage
is the serialized PrivateMessageContent:
opaque application_data<V>; // varint-prefixed payload
opaque signature<V>; // FramedContentAuthData.signature
opaque padding[N]; // zero padding (N may be 0)
where the signature is `SignWithLabel(., "FramedContentTBS",
FramedContentTBS)` computed over (version || wire_format ||
FramedContent || GroupContext). This change wires both sides up
end-to-end:
* encrypt now builds FramedContentTBS via the new
buildApplicationFramedContentTbs helper, signs it with the caller's
signature private key, and writes application_data<V> || signature<V>
(zero-length padding) as the AEAD plaintext.
* decrypt parses application_data<V> and signature<V> from the AEAD
plaintext, asserts all remaining bytes are zero padding, rebuilds
FramedContentTBS for the sender's leaf, and verifies the Ed25519
signature against that leaf's signatureKey before returning the
application payload. Non-application PrivateMessages now error
(commit/proposal-inside-PrivateMessage was never correctly handled
by the old raw-bytes path either; leaving that for a follow-up).
Un-Ignore MdkWelcomeInteropTest.testBobDecryptsMdkApplicationMessagesFromAlice
— Amethyst now decrypts and verifies each of the three openmls-authored
application messages against Alice's committer leaf.
https://claude.ai/code/session_01HfHdd5S5rvxUW2ihEpLGJr