- Drop X3DH tests
- Drop cross-version tests with libsignal v0.12 and v0.21
- Change session benchmarks to use PQXDH, which is relevant if doing
comparisons before/after this commit.
This known-answer test was originally ported over from
libsignal-protocol-java, but did not produce the same results. Why?
Because one of the private keys chosen by libsignal-protocol-java for
its test was unclamped, while libsignal-the-Rust-implementation always
clamps private keys as part of deserialization, not just generation.
Consequently, the public key didn't actually correspond to the private
key according to our modern libsignal.
Rather than try to line up exactly with what libsignal-protocol-java
was coincidentally doing, this commit clamps the private key, computes
the resulting public key, and verifies a new
consistent-between-both-sides outcome.
This PR integrates a post-quantum ratchet (SPQR) into libsignal, using an API that maintains its own internal chain and provides per-message keys. In doing so, it also aims to be fully backwards-compatible with current clients and stored session state.
## Backwards compatibility with current clients
Remote clients that connect to us or that we connect to may not have this integration. If they don't, their SignalMessage wire format should still deserialize, and in doing so we'll receive an empty pq_ratchet field. SQPR handles this internally, by downgrading the protocol version to "version 0" or "don't do anything". Note that should we eventually want to disallow this, we can do so via increasing the `min_version` field passed into the SQPR init functions to V1. This is also the method by which we would upgrade SQPR from v1 to a future v2, etc.
## Opt-in
The publicly facing API calls for this now expose an explicit opt-in via a passed-in `use_pq_ratchet` bool (and associated enums in language-specific APIs). If false, they default to SQPR `v0`, IE: none. If true, they try to set up SPQR on new sessions, but will downgrade if the remote party cannot or will not do the same.
This fixes a bug introduced by cd36118 where starting a new session
locally would prevent incoming messages on a previous session from
being decrypted if that session hadn't advanced past the "pre-key"
stage. Fix this by promoting the session *before* successful
decryption instead of after; since we won't *save* the promotion
unless the message decrypts successfully, there's ultimately no change
in either the failure or success cases *except* when hitting this bug.
The format hasn't changed, so we don't need to bump the version number
for the messages the server sends to recipients.
This is implemented in two places: the Rust side for round-trip
testing, and the Java side for what the server actually does. (Both
are implemented to avoid unnecessary copies and unfortunately the two
aren't conveniently compatible with one another, but it's a simple
implementation anyway.)
There should be no reason for a client to split up devices of the same
recipient in a non-contiguous manner, but since we'd have to check it
anyway, we might as well accept it. (Duplicating devices within a
recipient can then be checked separately.)
For the most part this should happen transparently without any
explicit adoption, like the previous change, but for Java code the
NoSessionException is now properly declared on SessionCipher.encrypt.
(This was always technically possible, but clients were expected to
have previously checked for session validity before using
SessionCipher; now that there's an expiration involved, that's not
strictly possible.)
Only the iOS client ever used this extra parameter, and it's one
that's easily stored alongside the reference to a store. This is
massively simpler than having it threaded down to the Rust
libsignal_protocol and back up through the bridging layer.
Anything that stays within the crate gets a dedicated error type, or
no error at all if the operation cannot actually fail. The "defensive"
signatures remain for public operations.
Apart from making 'Result' more meaningful, this also keeps from
propagating low-level errors out that really indicate a corrupt
session.
The former was used for errors in the protobuf format itself, while
the latter was used when the decoded protobuf failed some higher-level
precondition. But apps can't really distinguish those cases, and
neither one "should" happen in a reliable system, so this is just
defending against rare or malicious inputs.
This commit mechanically turns every prost::DecodeError into
InvalidProtobufEncoding, but the next commit will make more of a
distinction of these errors.
This marks that a session is being opened by Alice to reply to Bob,
who has sent a message to Alice's phone number rather than her account
UUID. Apps can check this flag to determine if they need to include
extra information in the message content to certify that yes, this
account is the owner of this phone number. The state is automatically
cleared once the current session receives a response from Bob.
This dedicated error is thrown when a recipient has a registration ID
that's out of the range used by Signal [0, 0x3FFF]. These IDs cannot
be encoded in the sealed sender v2 format and are not supported, even
though they don't cause any problems for 1:1 messages.