Skip to content

Commit

Permalink
integrate the sealed sender markdown document from review comments
Browse files Browse the repository at this point in the history
  • Loading branch information
cosmicexplorer committed Oct 1, 2021
1 parent 20e89bb commit 96fa412
Showing 1 changed file with 110 additions and 31 deletions.
141 changes: 110 additions & 31 deletions rust/protocol/src/sealed_sender.rs
Original file line number Diff line number Diff line change
Expand Up @@ -798,18 +798,18 @@ pub async fn sealed_sender_encrypt<R: Rng + CryptoRng>(
sealed_sender_encrypt_from_usmc(destination, &usmc, identity_store, ctx, rng).await
}

/// This method implements a single-key single-recipient [KEM], described in [this Signal blog post]
/// on the sealed sender mechanism.
/// This method implements the single-key single-recipient [KEM] described in [this Signal blog
/// post], a.k.a Sealed Sender v1.
///
/// [KEM]: https://en.wikipedia.org/wiki/Key_encapsulation
/// [this Signal blog post]: https://signal.org/blog/sealed-sender/
///
/// # Contrast with Multi-Recipient KEM
/// # Contrast with Sealed Sender v2
/// The *single-recipient* KEM scheme implemented by this method partially derives the encryption
/// key from the recipient's identity key, which would then require re-encrypting the same message
/// multiple times to send to multiple recipients. In contrast,
/// [sealed_sender_multi_recipient_encrypt] uses a *multi-recipient* KEM scheme which avoids this
/// repeated work, but makes a few additional design tradeoffs.
/// [Sealed Sender v2][sealed_sender_multi_recipient_encrypt] uses a *multi-recipient* KEM scheme
/// which avoids this repeated work, but makes a few additional design tradeoffs.
///
/// # Overview
/// The KEM scheme implemented by this method is described in [this Signal blog post]. The
Expand All @@ -819,8 +819,8 @@ pub async fn sealed_sender_encrypt<R: Rng + CryptoRng>(
/// sender's public/private key pair.
/// 3. Symmetrically encrypt the sender's public key using the cipher key and MAC key from (2) with
/// AES-256 in CTR mode.
/// 4. Derive a second symmetric cipher key and MAC key from a salt composed of the sender's private
/// key, the recipient's public key, and the chain key from (2).
/// 4. Derive a second symmetric cipher key and MAC key from the sender's private key, the
/// recipient's public key, and the chain key from (2).
/// 5. Symmetrically encrypt the underlying [UnidentifiedSenderMessageContent] using the cipher key
/// and MAC key from (4) with AES-256 in CTR mode.
/// 6. Send the ephemeral public key from (1) and the encrypted public key from (3) to the
Expand All @@ -841,8 +841,12 @@ pub async fn sealed_sender_encrypt<R: Rng + CryptoRng>(
///```
///
/// ## Wire Format
/// The output of this method is encoded as a protobuf, prepended with an additional [u8] to
/// indicate the version of Sealed Sender in use.
/// The output of this method is encoded as an `UnidentifiedSenderMessage.Message` from
/// `sealed_sender.proto`, prepended with an additional byte to indicate the version of Sealed
/// Sender in use (see [further documentation on the version
/// byte][sealed_sender_multi_recipient_encrypt#the-version-byte]). In all cases,
/// [sealed_sender_decrypt] is used in the client to decrypt the Sealed Sender message, regardless
/// of version.
pub async fn sealed_sender_encrypt_from_usmc<R: Rng + CryptoRng>(
destination: &ProtocolAddress,
usmc: &UnidentifiedSenderMessageContent,
Expand Down Expand Up @@ -1070,45 +1074,45 @@ mod sealed_sender_v2 {
}

/// This method implements a single-key multi-recipient [KEM] as defined in Manuel Barbosa's
/// ["Randomness Reuse: Extensions and Improvements"].
/// ["Randomness Reuse: Extensions and Improvements"], a.k.a. Sealed Sender v2.
///
/// [KEM]: https://en.wikipedia.org/wiki/Key_encapsulation
/// ["Randomness Reuse: Extensions and Improvements"]: https://haslab.uminho.pt/mbb/files/reuse.pdf
///
/// # Contrast vs Single-Recipient KEM
/// The KEM scheme implemented by this method uses the "Generic Construction" in `4.1` of that
/// paper, instantiated with [ElGamal encryption]. This technique enables reusing a single sequence
/// of random bytes across multiple messages with the same content, which reduces computation time
/// for clients sending the same message to multiple recipients (without compromising the message
/// security).
/// There are a few additional design tradeoffs this method makes vs our [single-recipient KEM]
/// # Contrast with Sealed Sender v1
/// The KEM scheme implemented by this method uses the "Generic Construction" in `4.1` of [that
/// paper]["Randomness Reuse: Extensions and Improvements"], instantiated with [ElGamal
/// encryption]. This technique enables reusing a single sequence of random bytes across multiple
/// messages with the same content, which reduces computation time for clients sending the same
/// message to multiple recipients (without compromising the message security).
///
/// There are a few additional design tradeoffs this method makes vs [Sealed Sender v1]
/// which may make it comparatively unwieldy for certain scenarios:
/// 1. it requires a [SessionRecord] to exist already for the recipient, i.e. that a Double Ratchet
/// message chain has previously been established in the [SessionStore] via
/// [crate::process_prekey_bundle] after an initial [crate::PreKeySignalMessage] is recieved.
/// 2. it ferries a lot of additional information in its encoding which makes the resulting message
/// bulkier than the message produced by this method.
/// 3. unlike other message types sent over the wire, the encoded message returned by this method
/// does *not* correspond to a specific protobuf schema due to the complex web of cryptographic
/// data dependencies in different segments of the resulting encoded message (see [**Wire
/// Format**](#wire-format)).
/// does *not* correspond to a specific protobuf schema, in order to avoid inefficiencies
/// produced by protobuf's packing (see [**Wire Format**](#wire-format)).
///
/// [ElGamal encryption]: https://en.wikipedia.org/wiki/ElGamal_encryption
/// [single-recipient KEM]: sealed_sender_encrypt_from_usmc#contrast-with-multi-recipient-kem
/// [Sealed Sender v1]: sealed_sender_encrypt_from_usmc#contrast-with-sealed-sender-v2
///
/// # Overview
/// The high-level steps of this process are summarized below:
/// 1. Generate a series of random bytes.
/// 2. Derive an ephemeral key pair from (1).
/// 3. *Once per recipient:* Encrypt (1) using a shared secret derived from the private ephemeral
/// key (2) and the recipient's public identity key.
/// 4. *Once per recipient:* Add an authentication tag with for (3) using a secret derived from the
/// 4. *Once per recipient:* Add an authentication tag for (3) using a secret derived from the
/// sender's private identity key and the recipient's public identity key.
/// 5. Generate a symmetric key from (1) and use it to symmetrically encrypt the underlying
/// [UnidentifiedSenderMessageContent] via [AEAD encryption]. *This step is only performed once
/// per message, regardless of the number of recipients.*
/// 6. Send the public ephemeral key (2), the encrypted random bytes (3), and the authentication tag
/// (4) to the recipient, along with the encrypted message (5).
/// (4) to the server, along with the encrypted message (5).
///
/// [AEAD encryption]: https://en.wikipedia.org/wiki/Authenticated_encryption#Authenticated_encryption_with_associated_data_(AEAD)
///
Expand Down Expand Up @@ -1139,15 +1143,89 @@ mod sealed_sender_v2 {
/// return message
///```
///
/// ## Wire Format
/// The encoded result of this method has a [u8] prepended to the output so that the Signal backend
/// server can determine that the message was sent using the multi-recipient KEM scheme, split up
/// the set of messages by recipient, then securely route each individual message to its intended
/// recipient. Each individual such message is then decoded in the Signal client by calling
/// [sealed_sender_decrypt_to_usmc].
/// # Wire Format
/// Multi-recipient sealed-sender does not use protobufs for its payload format. Instead, it uses
/// a flat format marked with a [version byte](#the-version-byte). The format is different for
/// [sending] and [receiving]. The decrypted content is
/// a protobuf-encoded `UnidentifiedSenderMessage.Message` from `sealed_sender.proto`.
///
/// The public key used in Sealed Sender v2 is always a Curve25519 DJB key.
///
/// [sending]: #sent-messages
/// [receiving]: #received-messages
///
/// ## The version byte
///
/// Sealed sender messages (v1 and v2) in serialized form begin with a version [byte][u8].
/// This byte has the form:
///
/// ```text
/// (requiredVersion << 4) | currentVersion
/// ```
///
/// v1 messages thus have a version byte of `0x11`. v2 messages have a version byte
/// of `0x22`. A hypothetical version byte `0x34` would indicate a message encoded
/// as Sealed Sender v4, but decodable by any client that supports Sealed Sender v3.
///
/// ## Routing messages to recipients
///
/// The Signal backend server uses the initial version byte from the [sent message][sending] to
/// determine whether the message was sent using Sealed Sender v2. If so, the server will then split
/// up the set of messages and securely route each individual [received message][receiving] to its
/// intended recipient.
///
/// For testing purposes, [sealed_sender_multi_recipient_fan_out] can be used to convert such a bulk
/// message produced by this method into a sequence of encrypted [UnidentifiedSenderMessageContent].
/// message produced by Sealed Sender v2 into a sequence of [received messages][receiving]; however,
/// in doing so it will drop all of the metadata necessary to identify the message's
/// intended recipient.
///
/// ## Received messages
///
/// ```text
/// ReceivedMessage {
/// version_byte: u8,
/// c: [u8; 32],
/// at: [u8; 16],
/// e_pub: [u8; 32],
/// message: [u8] // remaining bytes
/// }
/// ```
///
/// Each individual Sealed Sender message received from the server is decoded in the Signal
/// client by calling [sealed_sender_decrypt].
///
/// ## Sent messages
///
/// ```text
/// PerRecipientData {
/// uuid: [u8; 16],
/// device_id: varint,
/// registration_id: u16,
/// c: [u8; 32],
/// at: [u8; 16],
/// }
///
/// SentMessage {
/// version_byte: u8,
/// count: varint,
/// recipients: [PerRecipientData; count],
/// e_pub: [u8; 32],
/// message: [u8] // remaining bytes
/// }
/// ```
///
/// The varint encoding used is the same as [protobuf's][varint]. Values are unsigned. UUIDs are
/// encoded per [RFC 4122], with the first eight bytes considered "most significant" [^1].
/// Fixed-width integers are in network byte order (big-endian).
///
/// [varint]: https://developers.google.com/protocol-buffers/docs/encoding#varints
/// [RFC 4122]: https://tools.ietf.org/html/rfc4122#section-4.1.2
///
/// [^1]: RFC 4122 guarantees the encoding order of the fields in a
/// UUID, but the representation of each field may vary based on the UUID's
/// "variant". For Sealed Sender's purposes, this is not important except for
/// debug-printing, since UUIDs are always treated as opaque identifiers matched
/// byte-for-byte.
pub async fn sealed_sender_multi_recipient_encrypt<R: Rng + CryptoRng>(
destinations: &[&ProtocolAddress],
destination_sessions: &[&SessionRecord],
Expand Down Expand Up @@ -1487,7 +1565,8 @@ impl SealedSenderDecryptionResult {
}
}

/// Decrypt a Sealed Sender message `ciphertext` and validate its sender certificate.
/// Decrypt a Sealed Sender message `ciphertext` in either the v1 or v2 format, and validate its
/// sender certificate.
///
/// This method calls [sealed_sender_decrypt_to_usmc] to extract the sender information, including
/// the embedded [SenderCertificate]. The sender certificate is then validated against the
Expand Down

0 comments on commit 96fa412

Please sign in to comment.