short version: the token/cert handshake that accompanies the exchange of sealed sender messages provides a form of "proof of consent" that the recipient of sealed sender messages wanted to receive them (either because they had the sender in their contact store, or because they had already sent the sender a message previously)
since singalboost (and under the hood, signald or signalc) do not currently support sealed sender messages, all of our messages are counted toward the rate limit, which currently is getting more stringent, and appears to have recently resulted in the blocking of our IP (which will likely get blocked again quickly if we move to another server)
this MR provides a fix to allow signalc to support sealed sender messages and thus prove to the signal server that our bots are not sending spam, and thus avoid rate limiting and blocking. (since it requires being implemented in signalc -- our custom signal client -- it will not take effect until signalc ships)
implementation sketch
handle incoming profile keys on every message (in SignalReceiver#dispatch) and upsert them into the PreKeyStore (do this on every message b/c we want to notice new profile keys when they are rotated -- which happens after a user blocks someone)
when we want to send a message, derive a Pair<UnidentifiedAccessKey> from stored profile keys (one member of the pair derrives from the channel's key, the other from
here, an UnidentifiedAccessKey is a "delivery token" + "sender certificate" tuple that allows the sealed sender authentication to work
the "pair" denotes that we will derrive one such tuple for ourselves and one for users to whom we want to send messages
we derrive members of the pair by (respecively) calling UnidentifiedAccess.deriveAccessKeyFrom(profileKey) on:
(1) the channel's profile key (stored in the signalc.store.AccountStore)
(2) the profile key we have stored for the subscriber in a (newly created) signalc.store.ContactStore)
we then include this pair as the 2nd argument to SignalServiceMessageSender#sendMessage (which is currently always null)
add our profile key to all outgoing messages to enable people to send us sealed sender messages (via SignalDataMessage.Builder.withProfileKey)
investigation notes
inventory
in code: UnidentifiedAccess(key, cert)
referred to in blog as: delivery token, sender certificate
spam prevention design
blog: "To prevent abuse, clients derive a 96-bit delivery token from their profile key and register it with the service. The service requires clients to prove knowledge of the delivery token for a user in order to transmit “sealed sender” messages to that user."
signal-android code dive
signal-android calls SignalMessageSender#sendMessage with an Optional.of(UniditentifedAccess), which has a key and a cert
note: in libsignal, the outer call to sendMessage expects a pair of UnidentifiedAccess with both a target and a "source" -> they extract the target and pass it to inner calls of #sendMessage
the key is used used as follows:
in SignalServiceMessageSender#getEncryptedMessages to identify target
in socket#send -> PushServiceSocket#buildServiceRequest to add a Unidentified-Access-Key header to the request
blog: "To prevent spoofing, clients periodically retrieve a short-lived sender certificate from the service attesting to their identity."
blog: "receiving clients can easily check its validity." (we think we don't have to implement this check b/c it is implicitly handled in underlying libsignal-client rust implementation)
facts:
to send sealed sender msg to subscriber, signalc needs a delivery token. to derive delivery token, it needs a profile key. to get a profile key, it has to ask for it from server.
"blocking a user who has access to a profile key will trigger a profile key rotation." -> we probably need to detect this state and fetch new profile keys (but we do this on every received message, and advertise to receivers on every sent message, so: check!)
signal-android includes its own profileKey in messages it sends
questions:
what makes a good UNIDENTIFIED_SENDER_TRUST_ROOT?
A: likely whatever Signal-Android uses, which we should check for expiry / key rotation (as we do for base cert in trust store)
where does "registration" of a signalc channel's delivery token happen? (as per quote in "spam prevention design" above)
A: perhaps it already happens w/ account manager construction?