Skip to main content

libsignal_protocol/
ratchet.rs

1//
2// Copyright 2020 Signal Messenger, LLC.
3// SPDX-License-Identifier: AGPL-3.0-only
4//
5
6mod keys;
7
8use rand::{CryptoRng, Rng};
9
10pub(crate) use self::keys::{ChainKey, MessageKeyGenerator, RootKey};
11use crate::handshake::Handshake;
12use crate::pqxdh::{HandshakeKeys, Pqxdh};
13// Re-export the parameter types for backward compatibility.
14// Callers (session.rs, tests) use these via `ratchet::`.
15pub use crate::pqxdh::{InitiatorParameters, RecipientParameters};
16use crate::protocol::CIPHERTEXT_MESSAGE_CURRENT_VERSION;
17use crate::state::SessionState;
18use crate::{KeyPair, Result, SessionRecord, SignalProtocolError, consts};
19
20// Backward-compatible aliases for the old names. These keep existing
21// external callers (tests, bridge code) compiling during the transition.
22#[doc(hidden)]
23pub type AliceSignalProtocolParameters = InitiatorParameters;
24#[doc(hidden)]
25pub type BobSignalProtocolParameters<'a> = RecipientParameters<'a>;
26
27fn spqr_chain_params(self_connection: bool) -> spqr::ChainParams {
28    #[allow(clippy::needless_update)]
29    spqr::ChainParams {
30        max_jump: if self_connection {
31            u32::MAX
32        } else {
33            consts::MAX_FORWARD_JUMPS.try_into().expect("should be <4B")
34        },
35        max_ooo_keys: consts::MAX_MESSAGE_KEYS.try_into().expect("should be <4B"),
36        ..Default::default()
37    }
38}
39
40/// Initialize a session from the initiator's side.
41///
42/// Performs the PQXDH key agreement and then sets up the Double Ratchet
43/// and SPQR state.
44pub(crate) fn initialize_alice_session<R: Rng + CryptoRng>(
45    parameters: &InitiatorParameters,
46    csprng: &mut R,
47) -> Result<SessionState> {
48    let (
49        kyber_ciphertext,
50        HandshakeKeys {
51            root_key,
52            chain_key,
53            pqr_key,
54        },
55    ) = Pqxdh::initiate(parameters, csprng)?;
56
57    initialize_initiator_session(
58        parameters,
59        root_key,
60        chain_key,
61        pqr_key,
62        kyber_ciphertext,
63        csprng,
64    )
65}
66
67fn initialize_initiator_session<R: Rng + CryptoRng>(
68    parameters: &InitiatorParameters,
69    root_key: RootKey,
70    chain_key: ChainKey,
71    pqr_key: [u8; 32],
72    kyber_ciphertext: crate::kem::SerializedCiphertext,
73    csprng: &mut R,
74) -> Result<SessionState> {
75    let local_identity = parameters.our_identity_key_pair().identity_key();
76
77    let sending_ratchet_key = KeyPair::generate(csprng);
78    let (sending_chain_root_key, sending_chain_chain_key) = root_key.create_chain(
79        parameters.their_ratchet_key(),
80        &sending_ratchet_key.private_key,
81    )?;
82
83    let pqr_state = spqr::initial_state(spqr::Params {
84        auth_key: &pqr_key,
85        version: spqr::Version::V1,
86        direction: spqr::Direction::A2B,
87        min_version: spqr::Version::V1, // Require that all clients speak SPQR
88        chain_params: spqr_chain_params(parameters.self_session()),
89    })
90    .map_err(|e| {
91        // Since this is an error associated with the initial creation of the state,
92        // it must be a problem with the arguments provided.
93        SignalProtocolError::InvalidArgument(format!(
94            "post-quantum ratchet: error creating initial A2B state: {e}"
95        ))
96    })?;
97
98    let mut session = SessionState::new(
99        CIPHERTEXT_MESSAGE_CURRENT_VERSION,
100        local_identity,
101        parameters.their_identity_key(),
102        &sending_chain_root_key,
103        &parameters.our_ephemeral_key_pair().public_key,
104        pqr_state,
105    )
106    .with_receiver_chain(parameters.their_ratchet_key(), &chain_key)
107    .with_sender_chain(&sending_ratchet_key, &sending_chain_chain_key);
108
109    session.set_kyber_ciphertext(kyber_ciphertext);
110
111    Ok(session)
112}
113
114/// Initialize a session from the recipient's side.
115///
116/// Performs the PQXDH key agreement and then sets up the Double Ratchet
117/// and SPQR state.
118pub(crate) fn initialize_bob_session(
119    parameters: &RecipientParameters,
120    our_ratchet_key_pair: &KeyPair,
121) -> Result<SessionState> {
122    let HandshakeKeys {
123        root_key,
124        chain_key,
125        pqr_key,
126    } = Pqxdh::accept(parameters)?;
127
128    initialize_recipient_session(
129        parameters,
130        our_ratchet_key_pair,
131        root_key,
132        chain_key,
133        pqr_key,
134    )
135}
136
137fn initialize_recipient_session(
138    parameters: &RecipientParameters,
139    our_ratchet_key_pair: &KeyPair,
140    root_key: RootKey,
141    chain_key: ChainKey,
142    pqr_key: [u8; 32],
143) -> Result<SessionState> {
144    let local_identity = parameters.our_identity_key_pair().identity_key();
145
146    let pqr_state = spqr::initial_state(spqr::Params {
147        auth_key: &pqr_key,
148        version: spqr::Version::V1,
149        direction: spqr::Direction::B2A,
150        min_version: spqr::Version::V1, // Require that all clients speak SPQR
151        chain_params: spqr_chain_params(parameters.self_session()),
152    })
153    .map_err(|e| {
154        // Since this is an error associated with the initial creation of the state,
155        // it must be a problem with the arguments provided.
156        SignalProtocolError::InvalidArgument(format!(
157            "post-quantum ratchet: error creating initial B2A state: {e}"
158        ))
159    })?;
160
161    let session = SessionState::new(
162        CIPHERTEXT_MESSAGE_CURRENT_VERSION,
163        local_identity,
164        parameters.their_identity_key(),
165        &root_key,
166        parameters.their_ephemeral_key(),
167        pqr_state,
168    )
169    .with_sender_chain(our_ratchet_key_pair, &chain_key);
170
171    Ok(session)
172}
173
174pub fn initialize_alice_session_record<R: Rng + CryptoRng>(
175    parameters: &InitiatorParameters,
176    csprng: &mut R,
177) -> Result<SessionRecord> {
178    Ok(SessionRecord::new(initialize_alice_session(
179        parameters, csprng,
180    )?))
181}
182
183pub fn initialize_bob_session_record(
184    parameters: &RecipientParameters,
185    our_ratchet_key_pair: &KeyPair,
186) -> Result<SessionRecord> {
187    Ok(SessionRecord::new(initialize_bob_session(
188        parameters,
189        our_ratchet_key_pair,
190    )?))
191}