diff --git a/zssp/src/main.rs b/zssp/src/main.rs index f22f9c3a1..51a98e820 100644 --- a/zssp/src/main.rs +++ b/zssp/src/main.rs @@ -14,6 +14,13 @@ struct TestApplication { } impl zssp::ApplicationLayer for TestApplication { + const REKEY_AFTER_USES: u64 = 131072; + const EXPIRE_AFTER_USES: u64 = 2147483648; + const REKEY_AFTER_TIME_MS: i64 = 1000 * 60 * 60 * 2; + const REKEY_AFTER_TIME_MS_MAX_JITTER: u32 = 1000 * 60 * 10; + const INCOMING_SESSION_NEGOTIATION_TIMEOUT_MS: i64 = 2000; + const RETRY_INTERVAL: i64 = 500; + type Data = (); type IncomingPacketBuffer = Vec; diff --git a/zssp/src/zssp.rs b/zssp/src/zssp.rs index 20d7d58bd..4419bd0bc 100644 --- a/zssp/src/zssp.rs +++ b/zssp/src/zssp.rs @@ -133,6 +133,7 @@ struct SessionKey { created_at_counter: u64, // Counter at which session was created rekey_at_counter: u64, // Rekey at or after this counter expire_at_counter: u64, // Hard error when this counter value is reached or exceeded + ratchet_count: u64, // Number of rekey events bob: bool, // Was this side "Bob" in this exchange? } @@ -926,8 +927,13 @@ impl Context { { let mut state = session.state.write().unwrap(); let _ = state.remote_session_id.insert(bob_session_id); - let _ = - state.keys[0].insert(SessionKey::new::(noise_es_ee_se_hk_psk, current_time, 2, false)); + let _ = state.keys[0].insert(SessionKey::new::( + noise_es_ee_se_hk_psk, + 1, + current_time, + 2, + false, + )); state.current_key = 0; state.current_offer = Offer::None; } @@ -1078,7 +1084,7 @@ impl Context { state: RwLock::new(State { remote_session_id: Some(incoming.alice_session_id), keys: [ - Some(SessionKey::new::(noise_es_ee_se_hk_psk, current_time, 2, true)), + Some(SessionKey::new::(noise_es_ee_se_hk_psk, 1, current_time, 2, true)), None, ], current_key: 0, @@ -1111,7 +1117,8 @@ impl Context { if let Some(session) = session { let state = session.state.read().unwrap(); if let Some(key) = state.keys[key_index].as_ref() { - // Only the current "Alice" accepts rekeys initiated by the current "Bob." + // Only the current "Alice" accepts rekeys initiated by the current "Bob." These roles + // flip with each rekey event. if !key.bob { let mut c = key.get_receive_cipher(); c.reset_init_gcm(&incoming_message_nonce); @@ -1142,10 +1149,16 @@ impl Context { send(Some(&session), &mut reply_buf); + // The new "Bob" doesn't know yet if Alice has received the new key, so the + // new key is recorded as the "alt" (key_index ^ 1) but the current key is + // not advanced yet. This happens automatically the first time we receive a + // valid packet with the new key. + let next_ratchet_count = key.ratchet_count + 1; drop(state); let mut state = session.state.write().unwrap(); let _ = state.keys[key_index ^ 1].replace(SessionKey::new::( next_session_key, + next_ratchet_count, current_time, counter.get(), false, @@ -1192,11 +1205,16 @@ impl Context { )); if secure_eq(&pkt.next_key_fingerprint, &SHA384::hash(next_session_key.as_bytes())) { + // The new "Alice" knows Bob has the key since this is an ACK, so she can go + // ahead and set current_key to the new key. Then when she sends something + // to Bob the other side will automatically advance to the new key as well. + let next_ratchet_count = key.ratchet_count + 1; drop(state); let next_key_index = key_index ^ 1; let mut state = session.state.write().unwrap(); let _ = state.keys[next_key_index].replace(SessionKey::new::( next_session_key, + next_ratchet_count, current_time, session.send_counter.load(Ordering::Acquire), true, @@ -1297,6 +1315,16 @@ impl Session { state.keys[state.current_key].is_some() } + /// Get the ratchet count and a hash fingerprint of the current active key. + pub fn key_info(&self) -> Option<(u64, [u8; 48])> { + let state = self.state.read().unwrap(); + if let Some(key) = state.keys[state.current_key].as_ref() { + Some((key.ratchet_count, SHA384::hash(key.ratchet_key.as_bytes()))) + } else { + None + } + } + /// Send a rekey init message. /// /// This is called from the session context's service() method when it's time to rekey. @@ -1473,7 +1501,13 @@ fn assemble_fragments_into(fragments: &[A::IncomingPacketBu } impl SessionKey { - fn new(key: Secret, current_time: i64, current_counter: u64, role_is_bob: bool) -> Self { + fn new( + key: Secret, + ratchet_count: u64, + current_time: i64, + current_counter: u64, + role_is_bob: bool, + ) -> Self { let a2b = kbkdf::(key.as_bytes()); let b2a = kbkdf::(key.as_bytes()); let (receive_key, send_key) = if role_is_bob { @@ -1496,6 +1530,7 @@ impl SessionKey { created_at_counter: current_counter, rekey_at_counter: current_counter.checked_add(Application::REKEY_AFTER_USES).unwrap(), expire_at_counter: current_counter.checked_add(Application::EXPIRE_AFTER_USES).unwrap(), + ratchet_count, bob: role_is_bob, } }