Skip to content

v2.8.0

Latest

Choose a tag to compare

@github-actions github-actions released this 15 Sep 21:08
· 5 commits to main since this release

Security

  • Group signature verification is now mandatory. Both ValidateGroupMessage and
    ProcessDistributionMessage previously skipped verification when a message carried no
    signature, treating it as valid. Because SenderIdentityKey is public and the transport
    is untrusted, an attacker able to write to the transport could send an unsigned
    sender-key distribution bearing a member's identity key and an attacker-chosen chain
    key, then impersonate that member. No sending path in the library ever produced an
    unsigned message, so this rejects only forgeries. (One receive path,
    LibEmiddleClient.Helpers.cs's RouteGroupMessageAsync, does construct an
    EncryptedGroupMessage without a Signature — but it also omits
    RotationEpoch, taking the type's default of 0 against an always-non-zero real value,
    which already broke the AES associated data and made it unable to decrypt anything before
    this release. Nothing in the library sends that message type and no test exercises it; it
    is a pre-existing, non-functional route, not a consequence of this change.)
  • Null ciphertext no longer crashes the process (denial of service). A malformed group
    message with a null Ciphertext, a valid member's SenderIdentityKey, and a well-formed
    nonce reached the AES decryption path uncaught: Ciphertext?.Length == 0 is false when
    Ciphertext is null, so the guard intended to reject empty ciphertext accepted null, and
    the resulting ArgumentNullException propagated out of DecryptMessageAsync because the
    wrapping try had only a finally, no catch. This was remotely triggerable by any
    party able to submit a group message. Null ciphertext is now rejected explicitly before
    it reaches the signing/decryption path.
  • Wrong-length signature no longer crashes the process (denial of service). A malformed
    group message could carry a Signature that was non-null and non-empty but not exactly
    64 bytes. ValidateGroupMessage and ProcessDistributionMessage rejected only a null or
    zero-length signature, so a wrong-length one reached Sodium.SignVerifyDetached, which
    throws ArgumentException rather than returning false for any length other than
    Constants.ED25519_SIGNATURE_SIZE. That exception propagated out of
    DecryptMessageAsync/ProcessDistributionMessage uncaught, crashing the receiving
    session. Both guards now reject a wrong-length signature the same way they reject an
    absent one.
  • Replay-set eviction is now insertion-ordered. Eviction used
    ConcurrentDictionary.Keys.Take(), which guarantees no ordering, so a recently seen
    message ID could be evicted while older ones survived.

Breaking

  • Group signature format changed. Fields in the signed byte string are now
    length-prefixed with a 4-byte big-endian length, SenderIdentityKey is always
    included, and a one-byte domain-separation tag (0x01 for messages, 0x02 for
    sender-key distributions) is prepended to the signed payload. Previously fields were
    concatenated without prefixes, so distinct field decompositions could produce identical
    signing input and a signature did not uniquely commit to the message it covered; and
    without the domain tag, a byte string valid for one payload type could in principle
    parse as the other, since both signing contexts share a single Ed25519 identity key. Not
    exploitable in the shipped code today, but closed now because the wire format is about
    to be frozen by golden vectors.

    2.8.0 peers cannot verify group messages or sender-key distributions from 2.7.x
    peers, or vice versa.
    All members of a group must upgrade together. One-to-one chat
    sessions are unaffected.

    This failure is silent. Group messages carry no protocol-version field, and
    ProtocolVersion.IsCompatible treats every 2.x peer as compatible, so a mixed-version
    group does not detect the mismatch. Cross-version messages are rejected one at a time and
    DecryptMessageAsync returns null — no exception reaches the caller. The rejections are
    recorded via LoggingManager.LogSecurityEvent (message: "signature verification failed"),
    so enable security-event logging before upgrading a group incrementally, or the
    symptom is simply that some members stop receiving messages with no error surfaced.

    Note on version numbering. VERSIONING.md reserves MAJOR for "breaking changes to
    the public API or protocol", so by that rule this change belongs in 3.0.0. It ships as a
    minor deliberately: the public API is unchanged, and the backward-compatible security
    fixes above were judged too important to hold behind the v3.0 milestone. Do not infer
    from the version number that 2.7.x and 2.8.0 group members interoperate — they do not.