libsignal_protocol/
ratchet.rs

1//
2// Copyright 2020 Signal Messenger, LLC.
3// SPDX-License-Identifier: AGPL-3.0-only
4//
5
6mod keys;
7mod params;
8
9use rand::{CryptoRng, Rng};
10
11pub(crate) use self::keys::{ChainKey, MessageKeyGenerator, RootKey};
12pub use self::params::{AliceSignalProtocolParameters, BobSignalProtocolParameters, UsePQRatchet};
13use crate::protocol::CIPHERTEXT_MESSAGE_CURRENT_VERSION;
14use crate::state::SessionState;
15use crate::{consts, KeyPair, Result, SessionRecord, SignalProtocolError};
16
17type InitialPQRKey = [u8; 32];
18
19fn derive_keys(secret_input: &[u8]) -> (RootKey, ChainKey, InitialPQRKey) {
20    derive_keys_with_label(
21        b"WhisperText_X25519_SHA-256_CRYSTALS-KYBER-1024",
22        secret_input,
23    )
24}
25
26fn derive_keys_with_label(label: &[u8], secret_input: &[u8]) -> (RootKey, ChainKey, InitialPQRKey) {
27    let mut secrets = [0; 96];
28    hkdf::Hkdf::<sha2::Sha256>::new(None, secret_input)
29        .expand(label, &mut secrets)
30        .expect("valid length");
31    let (root_key_bytes, chain_key_bytes, pqr_bytes) =
32        (&secrets[0..32], &secrets[32..64], &secrets[64..96]);
33
34    let root_key = RootKey::new(root_key_bytes.try_into().expect("correct length"));
35    let chain_key = ChainKey::new(chain_key_bytes.try_into().expect("correct length"), 0);
36    let pqr_key: InitialPQRKey = pqr_bytes.try_into().expect("correct length");
37
38    (root_key, chain_key, pqr_key)
39}
40
41fn spqr_chain_params(self_connection: bool) -> spqr::ChainParams {
42    #[allow(clippy::needless_update)]
43    spqr::ChainParams {
44        max_jump: if self_connection {
45            u32::MAX
46        } else {
47            consts::MAX_FORWARD_JUMPS.try_into().expect("should be <4B")
48        },
49        max_ooo_keys: consts::MAX_MESSAGE_KEYS.try_into().expect("should be <4B"),
50        ..Default::default()
51    }
52}
53
54pub(crate) fn initialize_alice_session<R: Rng + CryptoRng>(
55    parameters: &AliceSignalProtocolParameters,
56    mut csprng: &mut R,
57) -> Result<SessionState> {
58    let local_identity = parameters.our_identity_key_pair().identity_key();
59
60    let mut secrets = Vec::with_capacity(32 * 6);
61
62    secrets.extend_from_slice(&[0xFFu8; 32]); // "discontinuity bytes"
63
64    let our_base_private_key = parameters.our_base_key_pair().private_key;
65
66    secrets.extend_from_slice(
67        &parameters
68            .our_identity_key_pair()
69            .private_key()
70            .calculate_agreement(parameters.their_signed_pre_key())?,
71    );
72
73    secrets.extend_from_slice(
74        &our_base_private_key.calculate_agreement(parameters.their_identity_key().public_key())?,
75    );
76
77    secrets.extend_from_slice(
78        &our_base_private_key.calculate_agreement(parameters.their_signed_pre_key())?,
79    );
80
81    if let Some(their_one_time_prekey) = parameters.their_one_time_pre_key() {
82        secrets
83            .extend_from_slice(&our_base_private_key.calculate_agreement(their_one_time_prekey)?);
84    }
85
86    let kyber_ciphertext = {
87        let (ss, ct) = parameters.their_kyber_pre_key().encapsulate(&mut csprng)?;
88        secrets.extend_from_slice(ss.as_ref());
89        ct
90    };
91
92    let (root_key, chain_key, pqr_key) = derive_keys(&secrets);
93
94    let sending_ratchet_key = KeyPair::generate(&mut csprng);
95    let (sending_chain_root_key, sending_chain_chain_key) = root_key.create_chain(
96        parameters.their_ratchet_key(),
97        &sending_ratchet_key.private_key,
98    )?;
99
100    let self_session = local_identity == parameters.their_identity_key();
101    let pqr_state = match parameters.use_pq_ratchet() {
102        UsePQRatchet::Yes => spqr::initial_state(spqr::Params {
103            auth_key: &pqr_key,
104            version: spqr::Version::V1,
105            direction: spqr::Direction::A2B,
106            // Set min_version to V0 (allow fallback to no PQR at all) while
107            // there are clients that don't speak PQR.  Once all clients speak
108            // PQR, we can up this to V1 to require that all subsequent sessions
109            // use at least V1.
110            min_version: spqr::Version::V0,
111            chain_params: spqr_chain_params(self_session),
112        })
113        .map_err(|e| {
114            // Since this is an error associated with the initial creation of the state,
115            // it must be a problem with the arguments provided.
116            SignalProtocolError::InvalidArgument(format!(
117                "post-quantum ratchet: error creating initial A2B state: {e}"
118            ))
119        })?,
120        UsePQRatchet::No => spqr::SerializedState::new(), // empty
121    };
122
123    let mut session = SessionState::new(
124        CIPHERTEXT_MESSAGE_CURRENT_VERSION,
125        local_identity,
126        parameters.their_identity_key(),
127        &sending_chain_root_key,
128        &parameters.our_base_key_pair().public_key,
129        pqr_state,
130    )
131    .with_receiver_chain(parameters.their_ratchet_key(), &chain_key)
132    .with_sender_chain(&sending_ratchet_key, &sending_chain_chain_key);
133
134    session.set_kyber_ciphertext(kyber_ciphertext);
135
136    Ok(session)
137}
138
139pub(crate) fn initialize_bob_session(
140    parameters: &BobSignalProtocolParameters,
141) -> Result<SessionState> {
142    let local_identity = parameters.our_identity_key_pair().identity_key();
143
144    let mut secrets = Vec::with_capacity(32 * 6);
145
146    secrets.extend_from_slice(&[0xFFu8; 32]); // "discontinuity bytes"
147
148    secrets.extend_from_slice(
149        &parameters
150            .our_signed_pre_key_pair()
151            .private_key
152            .calculate_agreement(parameters.their_identity_key().public_key())?,
153    );
154
155    secrets.extend_from_slice(
156        &parameters
157            .our_identity_key_pair()
158            .private_key()
159            .calculate_agreement(parameters.their_base_key())?,
160    );
161
162    secrets.extend_from_slice(
163        &parameters
164            .our_signed_pre_key_pair()
165            .private_key
166            .calculate_agreement(parameters.their_base_key())?,
167    );
168
169    if let Some(our_one_time_pre_key_pair) = parameters.our_one_time_pre_key_pair() {
170        secrets.extend_from_slice(
171            &our_one_time_pre_key_pair
172                .private_key
173                .calculate_agreement(parameters.their_base_key())?,
174        );
175    }
176
177    secrets.extend_from_slice(
178        &parameters
179            .our_kyber_pre_key_pair()
180            .secret_key
181            .decapsulate(parameters.their_kyber_ciphertext())?,
182    );
183
184    let (root_key, chain_key, pqr_key) = derive_keys(&secrets);
185
186    let self_session = local_identity == parameters.their_identity_key();
187    let pqr_state = match parameters.use_pq_ratchet() {
188        UsePQRatchet::Yes => spqr::initial_state(spqr::Params {
189            auth_key: &pqr_key,
190            version: spqr::Version::V1,
191            direction: spqr::Direction::B2A,
192            // Set min_version to V0 (allow fallback to no PQR at all) while
193            // there are clients that don't speak PQR.  Once all clients speak
194            // PQR, we can up this to V1 to require that all subsequent sessions
195            // use at least V1.
196            min_version: spqr::Version::V0,
197            chain_params: spqr_chain_params(self_session),
198        })
199        .map_err(|e| {
200            // Since this is an error associated with the initial creation of the state,
201            // it must be a problem with the arguments provided.
202            SignalProtocolError::InvalidArgument(format!(
203                "post-quantum ratchet: error creating initial B2A state: {e}"
204            ))
205        })?,
206        UsePQRatchet::No => spqr::SerializedState::new(), // empty
207    };
208    let session = SessionState::new(
209        CIPHERTEXT_MESSAGE_CURRENT_VERSION,
210        local_identity,
211        parameters.their_identity_key(),
212        &root_key,
213        parameters.their_base_key(),
214        pqr_state,
215    )
216    .with_sender_chain(parameters.our_ratchet_key_pair(), &chain_key);
217
218    Ok(session)
219}
220
221pub fn initialize_alice_session_record<R: Rng + CryptoRng>(
222    parameters: &AliceSignalProtocolParameters,
223    csprng: &mut R,
224) -> Result<SessionRecord> {
225    Ok(SessionRecord::new(initialize_alice_session(
226        parameters, csprng,
227    )?))
228}
229
230pub fn initialize_bob_session_record(
231    parameters: &BobSignalProtocolParameters,
232) -> Result<SessionRecord> {
233    Ok(SessionRecord::new(initialize_bob_session(parameters)?))
234}