From 8b6c9051fbfb339b2c1ec85f2d738d8957e5b6fd Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Wed, 1 Mar 2023 14:01:30 -0500 Subject: [PATCH] Rekeying is now tested and works. --- zssp/src/applicationlayer.rs | 2 +- zssp/src/main.rs | 102 +++++++++++++++---------- zssp/src/proto.rs | 22 +++--- zssp/src/zssp.rs | 139 ++++++++++++++++++++--------------- 4 files changed, 158 insertions(+), 107 deletions(-) diff --git a/zssp/src/applicationlayer.rs b/zssp/src/applicationlayer.rs index 145510dcc..b9a1f1723 100644 --- a/zssp/src/applicationlayer.rs +++ b/zssp/src/applicationlayer.rs @@ -31,7 +31,7 @@ pub trait ApplicationLayer: Sized { /// Attempting to encrypt more than this many messages with a key will cause a hard error /// and the internal erasure of ephemeral key material. You'll only ever hit this if something /// goes wrong and rekeying fails. - const EXPIRE_AFTER_USES: u64 = 2147483648; + const EXPIRE_AFTER_USES: u64 = 2147483647; /// Start attempting to rekey after a key has been in use for this many milliseconds. /// diff --git a/zssp/src/main.rs b/zssp/src/main.rs index 51a98e820..26acbce93 100644 --- a/zssp/src/main.rs +++ b/zssp/src/main.rs @@ -14,7 +14,7 @@ struct TestApplication { } impl zssp::ApplicationLayer for TestApplication { - const REKEY_AFTER_USES: u64 = 131072; + const REKEY_AFTER_USES: u64 = 100000; 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; @@ -44,6 +44,9 @@ fn alice_main( let context = zssp::Context::::new(16); let mut data_buf = [0u8; 65536]; let mut next_service = ms_monotonic() + 500; + let mut last_ratchet_count = 0; + let test_data = [1u8; 10000]; + let mut up = false; let alice_session = context .open( @@ -62,51 +65,57 @@ fn alice_main( println!("[alice] opening session {}", alice_session.id.to_string()); - let test_data = [1u8; 10000]; - let mut up = false; - while run.load(Ordering::Relaxed) { - let pkt = alice_in.try_recv(); let current_time = ms_monotonic(); - - if let Ok(pkt) = pkt { - //println!("bob >> alice {}", pkt.len()); - match context.receive( - alice_app, - || true, - |s_public, _| Some((P384PublicKey::from_bytes(s_public).unwrap(), Secret::default(), ())), - |_, b| { - let _ = alice_out.send(b.to_vec()); - }, - &mut data_buf, - pkt, - TEST_MTU, - current_time, - ) { - Ok(zssp::ReceiveResult::Ok) => { - //println!("[alice] ok"); - } - Ok(zssp::ReceiveResult::OkData(_, _)) => { - //println!("[alice] received {}", data.len()); - } - Ok(zssp::ReceiveResult::OkNewSession(s)) => { - println!("[alice] new session {}", s.id.to_string()); - } - Ok(zssp::ReceiveResult::Rejected) => {} - Err(e) => { - println!("[alice] ERROR {}", e.to_string()); + loop { + let pkt = alice_in.try_recv(); + if let Ok(pkt) = pkt { + //println!("bob >> alice {}", pkt.len()); + match context.receive( + alice_app, + || true, + |s_public, _| Some((P384PublicKey::from_bytes(s_public).unwrap(), Secret::default(), ())), + |_, b| { + let _ = alice_out.send(b.to_vec()); + }, + &mut data_buf, + pkt, + TEST_MTU, + current_time, + ) { + Ok(zssp::ReceiveResult::Ok) => { + //println!("[alice] ok"); + } + Ok(zssp::ReceiveResult::OkData(_, data)) => { + //println!("[alice] received {}", data.len()); + } + Ok(zssp::ReceiveResult::OkNewSession(s)) => { + println!("[alice] new session {}", s.id.to_string()); + } + Ok(zssp::ReceiveResult::Rejected) => {} + Err(e) => { + println!("[alice] ERROR {}", e.to_string()); + } } + } else { + break; } } if up { + let ratchet_count = alice_session.key_info().unwrap().0; + if ratchet_count > last_ratchet_count { + last_ratchet_count = ratchet_count; + println!("[alice] new key! ratchet count {}", ratchet_count); + } + assert!(alice_session .send( |b| { let _ = alice_out.send(b.to_vec()); }, &mut data_buf[..TEST_MTU], - &test_data[..2048 + ((zerotier_crypto::random::xorshift64_random() as usize) % (test_data.len() - 2048))], + &test_data[..1000 + ((zerotier_crypto::random::xorshift64_random() as usize) % (test_data.len() - 1000))], ) .is_ok()); } else { @@ -137,6 +146,8 @@ fn bob_main( ) { let context = zssp::Context::::new(16); let mut data_buf = [0u8; 65536]; + let mut data_buf_2 = [0u8; TEST_MTU]; + let mut last_ratchet_count = 0; let mut last_speed_metric = ms_monotonic(); let mut next_service = last_speed_metric + 500; let mut transferred = 0u64; @@ -144,7 +155,7 @@ fn bob_main( let mut bob_session = None; while run.load(Ordering::Relaxed) { - let pkt = bob_in.recv_timeout(Duration::from_millis(10)); + let pkt = bob_in.recv_timeout(Duration::from_millis(100)); let current_time = ms_monotonic(); if let Ok(pkt) = pkt { @@ -164,9 +175,18 @@ fn bob_main( Ok(zssp::ReceiveResult::Ok) => { //println!("[bob] ok"); } - Ok(zssp::ReceiveResult::OkData(_, data)) => { + Ok(zssp::ReceiveResult::OkData(s, data)) => { //println!("[bob] received {}", data.len()); transferred += data.len() as u64; + assert!(s + .send( + |b| { + let _ = bob_out.send(b.to_vec()); + }, + &mut data_buf_2, + data.as_mut(), + ) + .is_ok()); } Ok(zssp::ReceiveResult::OkNewSession(s)) => { println!("[bob] new session {}", s.id.to_string()); @@ -179,6 +199,14 @@ fn bob_main( } } + if let Some(bob_session) = bob_session.as_ref() { + let ratchet_count = bob_session.key_info().unwrap().0; + if ratchet_count > last_ratchet_count { + last_ratchet_count = ratchet_count; + println!("[bob] new key! ratchet count {}", ratchet_count); + } + } + let speed_metric_elapsed = current_time - last_speed_metric; if speed_metric_elapsed >= 1000 { last_speed_metric = current_time; @@ -208,8 +236,8 @@ fn main() { let alice_app = TestApplication { identity_key: P384KeyPair::generate() }; let bob_app = TestApplication { identity_key: P384KeyPair::generate() }; - let (alice_out, bob_in) = mpsc::sync_channel::>(128); - let (bob_out, alice_in) = mpsc::sync_channel::>(128); + let (alice_out, bob_in) = mpsc::sync_channel::>(1024); + let (bob_out, alice_in) = mpsc::sync_channel::>(1024); thread::scope(|ts| { let alice_thread = ts.spawn(|| alice_main(&run, &alice_app, &bob_app, alice_out, alice_in)); diff --git a/zssp/src/proto.rs b/zssp/src/proto.rs index 210107efd..32510bd44 100644 --- a/zssp/src/proto.rs +++ b/zssp/src/proto.rs @@ -33,8 +33,8 @@ pub(crate) const PACKET_TYPE_DATA: u8 = 0; pub(crate) const PACKET_TYPE_ALICE_NOISE_XK_INIT: u8 = 1; pub(crate) const PACKET_TYPE_BOB_NOISE_XK_ACK: u8 = 2; pub(crate) const PACKET_TYPE_ALICE_NOISE_XK_ACK: u8 = 3; -pub(crate) const PACKET_TYPE_ALICE_REKEY_INIT: u8 = 4; -pub(crate) const PACKET_TYPE_BOB_REKEY_ACK: u8 = 5; +pub(crate) const PACKET_TYPE_REKEY_INIT: u8 = 4; +pub(crate) const PACKET_TYPE_REKEY_ACK: u8 = 5; pub(crate) const HEADER_SIZE: usize = 16; pub(crate) const HEADER_PROTECT_ENCRYPT_START: usize = 6; @@ -123,24 +123,26 @@ pub(crate) const ALICE_NOISE_XK_ACK_MIN_SIZE: usize = ALICE_NOISE_XK_ACK_ENC_STA #[allow(unused)] #[repr(C, packed)] -pub(crate) struct AliceRekeyInit { +pub(crate) struct RekeyInit { pub header: [u8; HEADER_SIZE], + pub session_protocol_version: u8, // -- start AES-GCM encrypted portion (using current key) pub alice_e: [u8; P384_PUBLIC_KEY_SIZE], // -- end AES-GCM encrypted portion pub gcm_mac: [u8; AES_GCM_TAG_SIZE], } -impl AliceRekeyInit { - pub const ENC_START: usize = HEADER_SIZE; +impl RekeyInit { + pub const ENC_START: usize = HEADER_SIZE + 1; pub const AUTH_START: usize = Self::ENC_START + P384_PUBLIC_KEY_SIZE; pub const SIZE: usize = Self::AUTH_START + AES_GCM_TAG_SIZE; } #[allow(unused)] #[repr(C, packed)] -pub(crate) struct BobRekeyAck { +pub(crate) struct RekeyAck { pub header: [u8; HEADER_SIZE], + pub session_protocol_version: u8, // -- start AES-GCM encrypted portion (using current key) pub bob_e: [u8; P384_PUBLIC_KEY_SIZE], pub next_key_fingerprint: [u8; SHA384_HASH_SIZE], @@ -148,8 +150,8 @@ pub(crate) struct BobRekeyAck { pub gcm_mac: [u8; AES_GCM_TAG_SIZE], } -impl BobRekeyAck { - pub const ENC_START: usize = HEADER_SIZE; +impl RekeyAck { + pub const ENC_START: usize = HEADER_SIZE + 1; pub const AUTH_START: usize = Self::ENC_START + P384_PUBLIC_KEY_SIZE + SHA384_HASH_SIZE; pub const SIZE: usize = Self::AUTH_START + AES_GCM_TAG_SIZE; } @@ -161,8 +163,8 @@ pub(crate) trait ProtocolFlatBuffer {} impl ProtocolFlatBuffer for AliceNoiseXKInit {} impl ProtocolFlatBuffer for BobNoiseXKAck {} //impl ProtocolFlatBuffer for NoiseXKAliceStaticAck {} -impl ProtocolFlatBuffer for AliceRekeyInit {} -impl ProtocolFlatBuffer for BobRekeyAck {} +impl ProtocolFlatBuffer for RekeyInit {} +impl ProtocolFlatBuffer for RekeyAck {} #[derive(Clone, Copy)] #[repr(C, packed)] diff --git a/zssp/src/zssp.rs b/zssp/src/zssp.rs index 4419bd0bc..76e07b830 100644 --- a/zssp/src/zssp.rs +++ b/zssp/src/zssp.rs @@ -119,7 +119,7 @@ struct OutgoingSessionInit { enum Offer { None, NoiseXKInit(Box), - RekeyInit(P384KeyPair, [u8; AliceRekeyInit::SIZE], AtomicI64), + RekeyInit(P384KeyPair, [u8; RekeyInit::SIZE], AtomicI64), } /// An ephemeral session key with expiration info. @@ -181,6 +181,7 @@ impl Context { && (current_time >= key.rekey_at_time || session.send_counter.load(Ordering::Relaxed) >= key.rekey_at_counter) { + drop(state); session.initiate_rekey(|b| send(&session, b), current_time); } } @@ -1106,8 +1107,8 @@ impl Context { } } - PACKET_TYPE_ALICE_REKEY_INIT => { - if pkt_assembled.len() != AliceRekeyInit::SIZE { + PACKET_TYPE_REKEY_INIT => { + if pkt_assembled.len() != RekeyInit::SIZE { return Err(Error::InvalidPacket); } if incoming.is_some() { @@ -1116,58 +1117,74 @@ 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." These roles - // flip with each rekey event. - if !key.bob { - let mut c = key.get_receive_cipher(); - c.reset_init_gcm(&incoming_message_nonce); - c.crypt_in_place(&mut pkt_assembled[AliceRekeyInit::ENC_START..AliceRekeyInit::AUTH_START]); - let aead_authentication_ok = c.finish_decrypt(&pkt_assembled[AliceRekeyInit::AUTH_START..]); - key.return_receive_cipher(c); + if let Some(remote_session_id) = state.remote_session_id { + if let Some(key) = state.keys[key_index].as_ref() { + // 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); + c.crypt_in_place(&mut pkt_assembled[RekeyInit::ENC_START..RekeyInit::AUTH_START]); + let aead_authentication_ok = c.finish_decrypt(&pkt_assembled[RekeyInit::AUTH_START..]); + key.return_receive_cipher(c); - if aead_authentication_ok { - let pkt: &AliceRekeyInit = byte_array_as_proto_buffer(&pkt_assembled).unwrap(); - if let Some(alice_e) = P384PublicKey::from_bytes(&pkt.alice_e) { - let bob_e_secret = P384KeyPair::generate(); - let next_session_key = Secret(hmac_sha512( - key.ratchet_key.as_bytes(), - bob_e_secret.agree(&alice_e).ok_or(Error::FailedAuthentication)?.as_bytes(), - )); + if aead_authentication_ok { + let pkt: &RekeyInit = byte_array_as_proto_buffer(&pkt_assembled).unwrap(); + if let Some(alice_e) = P384PublicKey::from_bytes(&pkt.alice_e) { + let bob_e_secret = P384KeyPair::generate(); + let next_session_key = Secret(hmac_sha512( + key.ratchet_key.as_bytes(), + bob_e_secret.agree(&alice_e).ok_or(Error::FailedAuthentication)?.as_bytes(), + )); - let mut reply_buf = [0u8; BobRekeyAck::SIZE]; - let reply: &mut BobRekeyAck = byte_array_as_proto_buffer_mut(&mut reply_buf).unwrap(); - reply.bob_e = *bob_e_secret.public_key_bytes(); - reply.next_key_fingerprint = SHA384::hash(next_session_key.as_bytes()); + let mut reply_buf = [0u8; RekeyAck::SIZE]; + let reply: &mut RekeyAck = byte_array_as_proto_buffer_mut(&mut reply_buf).unwrap(); + reply.session_protocol_version = SESSION_PROTOCOL_VERSION; + reply.bob_e = *bob_e_secret.public_key_bytes(); + reply.next_key_fingerprint = SHA384::hash(next_session_key.as_bytes()); - let counter = session.get_next_outgoing_counter().ok_or(Error::MaxKeyLifetimeExceeded)?; - let mut c = key.get_send_cipher(counter.get())?; - c.reset_init_gcm(&create_message_nonce(PACKET_TYPE_BOB_REKEY_ACK, counter.get())); - c.crypt_in_place(&mut reply_buf[BobRekeyAck::ENC_START..BobRekeyAck::AUTH_START]); - reply_buf[BobRekeyAck::AUTH_START..].copy_from_slice(&c.finish_encrypt()); - key.return_send_cipher(c); + let counter = session.get_next_outgoing_counter().ok_or(Error::MaxKeyLifetimeExceeded)?.get(); + set_packet_header( + &mut reply_buf, + 1, + 0, + PACKET_TYPE_REKEY_ACK, + u64::from(remote_session_id), + state.current_key, + counter, + ); - send(Some(&session), &mut reply_buf); + let mut c = key.get_send_cipher(counter)?; + c.reset_init_gcm(&create_message_nonce(PACKET_TYPE_REKEY_ACK, counter)); + c.crypt_in_place(&mut reply_buf[RekeyAck::ENC_START..RekeyAck::AUTH_START]); + reply_buf[RekeyAck::AUTH_START..].copy_from_slice(&c.finish_encrypt()); + key.return_send_cipher(c); - // 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, - )); + session.header_protection_cipher.encrypt_block_in_place( + &mut reply_buf[HEADER_PROTECT_ENCRYPT_START..HEADER_PROTECT_ENCRYPT_END], + ); + send(Some(&session), &mut reply_buf); - return Ok(ReceiveResult::Ok); + // 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, + false, + )); + + return Ok(ReceiveResult::Ok); + } } + return Err(Error::FailedAuthentication); } - return Err(Error::FailedAuthentication); } } return Err(Error::OutOfSequence); @@ -1176,8 +1193,8 @@ impl Context { } } - PACKET_TYPE_BOB_REKEY_ACK => { - if pkt_assembled.len() != BobRekeyAck::SIZE { + PACKET_TYPE_REKEY_ACK => { + if pkt_assembled.len() != RekeyAck::SIZE { return Err(Error::InvalidPacket); } if incoming.is_some() { @@ -1192,12 +1209,12 @@ impl Context { if key.bob { let mut c = key.get_receive_cipher(); c.reset_init_gcm(&incoming_message_nonce); - c.crypt_in_place(&mut pkt_assembled[BobRekeyAck::ENC_START..BobRekeyAck::AUTH_START]); - let aead_authentication_ok = c.finish_decrypt(&pkt_assembled[BobRekeyAck::AUTH_START..]); + c.crypt_in_place(&mut pkt_assembled[RekeyAck::ENC_START..RekeyAck::AUTH_START]); + let aead_authentication_ok = c.finish_decrypt(&pkt_assembled[RekeyAck::AUTH_START..]); key.return_receive_cipher(c); if aead_authentication_ok { - let pkt: &BobRekeyAck = byte_array_as_proto_buffer(&pkt_assembled).unwrap(); + let pkt: &RekeyAck = byte_array_as_proto_buffer(&pkt_assembled).unwrap(); if let Some(bob_e) = P384PublicKey::from_bytes(&pkt.bob_e) { let next_session_key = Secret(hmac_sha512( key.ratchet_key.as_bytes(), @@ -1333,8 +1350,9 @@ impl Session { fn initiate_rekey(&self, mut send: SendFunction, current_time: i64) { let rekey_e = P384KeyPair::generate(); - let mut rekey_buf = [0u8; AliceRekeyInit::SIZE]; - let pkt: &mut AliceRekeyInit = byte_array_as_proto_buffer_mut(&mut rekey_buf).unwrap(); + let mut rekey_buf = [0u8; RekeyInit::SIZE]; + let pkt: &mut RekeyInit = byte_array_as_proto_buffer_mut(&mut rekey_buf).unwrap(); + pkt.session_protocol_version = SESSION_PROTOCOL_VERSION; pkt.alice_e = *rekey_e.public_key_bytes(); let state = self.state.read().unwrap(); @@ -1342,9 +1360,9 @@ impl Session { if let Some(key) = state.keys[state.current_key].as_ref() { if let Some(counter) = self.get_next_outgoing_counter() { if let Ok(mut gcm) = key.get_send_cipher(counter.get()) { - gcm.reset_init_gcm(&create_message_nonce(PACKET_TYPE_ALICE_REKEY_INIT, counter.get())); - gcm.crypt_in_place(&mut rekey_buf[AliceRekeyInit::ENC_START..AliceRekeyInit::AUTH_START]); - rekey_buf[AliceRekeyInit::AUTH_START..].copy_from_slice(&gcm.finish_encrypt()); + gcm.reset_init_gcm(&create_message_nonce(PACKET_TYPE_REKEY_INIT, counter.get())); + gcm.crypt_in_place(&mut rekey_buf[RekeyInit::ENC_START..RekeyInit::AUTH_START]); + rekey_buf[RekeyInit::AUTH_START..].copy_from_slice(&gcm.finish_encrypt()); key.return_send_cipher(gcm); debug_assert!(rekey_buf.len() <= MIN_TRANSPORT_MTU); @@ -1352,15 +1370,18 @@ impl Session { &mut rekey_buf, 1, 0, - PACKET_TYPE_ALICE_REKEY_INIT, + PACKET_TYPE_REKEY_INIT, u64::from(remote_session_id), state.current_key, counter.get(), ); + drop(state); + + self.header_protection_cipher + .encrypt_block_in_place(&mut rekey_buf[HEADER_PROTECT_ENCRYPT_START..HEADER_PROTECT_ENCRYPT_END]); send(&mut rekey_buf); - drop(state); self.state.write().unwrap().current_offer = Offer::RekeyInit(rekey_e, rekey_buf, AtomicI64::new(current_time)); } }