diff --git a/examples/basic_test.rs b/examples/extensive_test.rs similarity index 94% rename from examples/basic_test.rs rename to examples/extensive_test.rs index 87335a1..f9b2f9a 100644 --- a/examples/basic_test.rs +++ b/examples/extensive_test.rs @@ -19,11 +19,11 @@ use rand_core::OsRng; use rand_core::RngCore; use zssp_proto::application::{ - AcceptAction, ApplicationLayer, RatchetState, RatchetStates, RatchetUpdate, Settings, RATCHET_SIZE, CryptoLayer, + AcceptAction, ApplicationLayer, CryptoLayer, RatchetState, RatchetStates, RatchetUpdate, Settings, RATCHET_SIZE, }; use zssp_proto::crypto::P384KeyPair; use zssp_proto::crypto_impl::{ - Aes256Crate, AesGcmCrate, RustKyber1024PrivateKey, P384CrateKeyPair, P384CratePublicKey, Sha512Crate, + Aes256Crate, AesGcmCrate, Kyber1024CratePrivateKey, P384CrateKeyPair, P384CratePublicKey, Sha512Crate, }; use zssp_proto::Session; @@ -61,7 +61,7 @@ impl CryptoLayer for TestApplication { type Hash = Sha512Crate; type PublicKey = P384CratePublicKey; type KeyPair = P384CrateKeyPair; - type Kem = RustKyber1024PrivateKey; + type Kem = Kyber1024CratePrivateKey; type SessionData = u128; } @@ -77,7 +77,11 @@ impl ApplicationLayer for &mut TestApplication { true } - fn check_accept_session(&mut self, remote_static_key: &P384CratePublicKey, identity: &[u8]) -> AcceptAction { + fn check_accept_session( + &mut self, + remote_static_key: &P384CratePublicKey, + identity: &[u8], + ) -> AcceptAction { AcceptAction { session_data: Some(1), responder_disallows_downgrade: true, @@ -305,7 +309,8 @@ fn bob_main( } if current_time >= next_service { - next_service = current_time + context.service(&mut bob_app, |_| Some((|b| bob_out.send(b).is_ok(), TEST_MTU))); + next_service = + current_time + context.service(&mut bob_app, |_| Some((|b| bob_out.send(b).is_ok(), TEST_MTU))); } } } @@ -321,11 +326,7 @@ fn core(time: u64, packet_success_rate: u32) { }; let bob_keypair = P384CrateKeyPair::generate(&mut OsRng); let bob_pubkey = bob_keypair.public_key(); - let bob_app = TestApplication { - time: Instant::now(), - name: "bob", - ratchets: Ratchets::new(), - }; + let bob_app = TestApplication { time: Instant::now(), name: "bob", ratchets: Ratchets::new() }; let (alice_out, bob_in) = mpsc::sync_channel::>(256); let (bob_out, alice_in) = mpsc::sync_channel::>(256); diff --git a/examples/ping_pong.rs b/examples/ping_pong.rs new file mode 100644 index 0000000..84b0f0e --- /dev/null +++ b/examples/ping_pong.rs @@ -0,0 +1,218 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +* +* (c) ZeroTier, Inc. +* https://www.zerotier.com/ +*/ + +use std::cell::RefCell; +use std::convert::Infallible; +use std::ops::DerefMut; +use std::sync::Arc; +use std::time::Instant; + +use zssp_proto::application::{ + AcceptAction, ApplicationLayer, CryptoLayer, RatchetState, RatchetStates, RatchetUpdate, RATCHET_SIZE, +}; +use zssp_proto::crypto::{rand_core::OsRng, P384KeyPair}; +use zssp_proto::crypto_impl::{ + Aes256Crate, AesGcmCrate, Kyber1024CratePrivateKey, P384CrateKeyPair, P384CratePublicKey, Sha512Crate, +}; +use zssp_proto::{Context, Session}; + +/// The MTU can go as low as 128 bytes, and it does not have to be constant either! +const TEST_MTU: usize = 512; + +struct MyApp { + time: Instant, + remote_peers_session: Option>>, +} + +/// We specify which crypto implementations to use here, you can use those provided by the +/// `crypto_impl` module or provide your own. +impl CryptoLayer for MyApp { + type Rng = OsRng; + type Prp = Aes256Crate; + type Aead = AesGcmCrate; + type Hash = Sha512Crate; + type PublicKey = P384CratePublicKey; + type KeyPair = P384CrateKeyPair; + type Kem = Kyber1024CratePrivateKey; + + type SessionData = (); +} +/// In this example for simplicity we won't be hooking up ratchet keys to a filesystem backend. +/// They are dropped and peers ignore if they are missing. +#[allow(unused)] +impl ApplicationLayer for &mut MyApp { + type Crypto = MyApp; + type StorageError = Infallible; + + fn hello_requires_recognized_ratchet(&mut self) -> bool { + false + } + + fn initiator_disallows_downgrade(&mut self, session: &Arc>) -> bool { + false + } + + fn check_accept_session(&mut self, remote_static_key: &P384CratePublicKey, identity: &[u8]) -> AcceptAction { + self.remote_peers_session.take(); + AcceptAction { + session_data: Some(()), + responder_disallows_downgrade: false, + responder_silently_rejects: false, + } + } + + fn restore_by_fingerprint( + &mut self, + ratchet_fingerprint: &[u8; RATCHET_SIZE], + ) -> Result, Infallible> { + Ok(None) + } + + fn restore_by_identity( + &mut self, + remote_static_key: &P384CratePublicKey, + session_data: &(), + ) -> Result, Infallible> { + Ok(None) + } + + fn save_ratchet_state( + &mut self, + remote_static_key: &P384CratePublicKey, + session_data: &(), + update_data: RatchetUpdate<'_>, + ) -> Result<(), Infallible> { + Ok(()) + } + + fn time(&mut self) -> i64 { + self.time.elapsed().as_millis() as i64 + } +} + +/// In this example protocol the two peers simply bounce the message "ping" and "pong" back and +/// forth forever. +fn process_message(decrypted_message: &[u8], peer_name: &'static str) -> Option> { + match decrypted_message { + b"ping" => { + println!("[{}]: pong", peer_name); + Some(b"pong".to_vec()) + } + b"pong" => { + println!("[{}]: ping", peer_name); + Some(b"ping".to_vec()) + } + _ => None, + } +} + +/// For simplicity's sake this function assumes there is only one remote peer we can talk to. +fn receive( + context: &mut Context, + app: &RefCell, + peer_name: &'static str, + recv_queue: &RefCell>>, + send_queue: &RefCell>>, +) { + if let Some(recv_packet) = recv_queue.borrow_mut().pop() { + let push_onto_send_queue = |packet: Vec| { + assert!(packet.len() <= TEST_MTU); + send_queue.borrow_mut().push(packet); + true + }; + + use zssp_proto::result::ReceiveOk::*; + use zssp_proto::result::SessionEvent::*; + let result = context.receive( + app.borrow_mut().deref_mut(), + push_onto_send_queue, + TEST_MTU, + |_| Some((push_onto_send_queue, TEST_MTU)), + &0, + recv_packet, + ); + let (session, reply_message) = match result { + Ok(Unassociated) => return, + Ok(Session(session, event)) => match event { + NewSession | NewDowngradedSession => { + println!("[{}]: session received", peer_name); + app.borrow_mut().remote_peers_session = Some(session); + return; + } + Data(data) => { + if let Some(reply) = process_message(&data, peer_name) { + (session, reply) + } else { + return; + } + } + Control => return, + Established => { + println!("[{}]: ping", peer_name); + (session, b"ping".to_vec()) + } + Rejected => return, + DowngradedRatchetKey => return, + }, + Err(e) => { + println!("ERROR {:?}", e); + return; + } + }; + + context + .send(&session, push_onto_send_queue, TEST_MTU, reply_message) + .unwrap(); + + // This example does not properly track time so we just call service on every update. + context.service(app.borrow_mut().deref_mut(), |_| Some((push_onto_send_queue, TEST_MTU))); + } +} + +/// We create two peers, Alice and Bob, then we have Alice initiate a ZSSP session with Bob, and +/// then we have Alice send the message "ping" to Bob. +/// Bob replies with the message "pong", which Alice replies to with "ping" and so on forever. +fn main() { + let alice_keypair = P384CrateKeyPair::generate(&mut OsRng); + let alice_app = RefCell::new(MyApp { time: Instant::now(), remote_peers_session: None }); + let alice_send_queue = RefCell::new(Vec::>::new()); + let mut alice_context = Context::::new(alice_keypair, OsRng); + + let bob_keypair = P384CrateKeyPair::generate(&mut OsRng); + let bob_pubkey = bob_keypair.public_key(); + let bob_app = RefCell::new(MyApp { time: Instant::now(), remote_peers_session: None }); + let bob_send_queue = RefCell::new(Vec::>::new()); + let mut bob_context = Context::::new(bob_keypair, OsRng); + + let result = alice_context.open( + alice_app.borrow_mut().deref_mut(), + |packet| { + assert!(packet.len() <= TEST_MTU); + alice_send_queue.borrow_mut().push(packet); + true + }, + TEST_MTU, + bob_pubkey, + (), + Vec::new(), + ); + alice_app.borrow_mut().remote_peers_session = Some(result.unwrap()); + println!("[Alice]: session opened"); + + for _ in 0..16 { + receive(&mut bob_context, &bob_app, "Bob", &alice_send_queue, &bob_send_queue); + + receive( + &mut alice_context, + &alice_app, + "Alice", + &bob_send_queue, + &alice_send_queue, + ); + } +} diff --git a/src/application.rs b/src/application.rs index ba1f018..de30df1 100644 --- a/src/application.rs +++ b/src/application.rs @@ -183,7 +183,11 @@ pub trait ApplicationLayer: Sized { /// To prevent desync, if this function specifies that we should connect, no other open session /// with the same remote peer must exist. Drop or call expire on any pre-existing sessions /// before returning. - fn check_accept_session(&mut self, remote_static_key: &::PublicKey, identity: &[u8]) -> AcceptAction; + fn check_accept_session( + &mut self, + remote_static_key: &::PublicKey, + identity: &[u8], + ) -> AcceptAction; /// Lookup a specific ratchet state based on its ratchet fingerprint. /// This function will be called whenever Alice attempts to connect to us with a non-empty @@ -241,7 +245,8 @@ pub trait ApplicationLayer: Sized { /// These are provided for debugging, logging or metrics purposes, and must be used for /// nothing else. Do not base protocol-level decisions upon the events passed to this function. #[cfg(feature = "logging")] - fn event_log(&mut self, event: LogEvent<'_, Self::Crypto>); + #[allow(unused)] + fn event_log(&mut self, event: LogEvent<'_, Self::Crypto>) {} } /// A collection of fields specifying how to complete the key exchange with a specific remote peer, diff --git a/src/context.rs b/src/context.rs index b5855d6..f5aa527 100644 --- a/src/context.rs +++ b/src/context.rs @@ -101,7 +101,10 @@ impl Context { static_remote_key: Crypto::PublicKey, session_data: Crypto::SessionData, identity: Vec, - ) -> Result>, OpenError> where App: ApplicationLayer{ + ) -> Result>, OpenError> + where + App: ApplicationLayer, + { mtu = mtu.max(MIN_TRANSPORT_MTU); if identity.len() > IDENTITY_MAX_SIZE { return Err(OpenError::IdentityTooLarge); @@ -138,7 +141,10 @@ impl Context { send_to: impl FnOnce(&Arc>) -> Option<(SendFn, usize)>, remote_address: &impl Hash, raw_fragment: Vec, - ) -> Result, ReceiveError> where App: ApplicationLayer { + ) -> Result, ReceiveError> + where + App: ApplicationLayer, + { use crate::result::FaultType::*; send_unassociated_mtu = send_unassociated_mtu.max(MIN_TRANSPORT_MTU); let ctx = &self.0; @@ -201,7 +207,12 @@ impl Context { let (p, _) = from_nonce(&pn); let ret = match p { PACKET_TYPE_DATA => { - received_payload_in_place::(&mut zeta, kid_recv, to_aes_nonce(&pn), &mut assembled_packet)?; + received_payload_in_place::( + &mut zeta, + kid_recv, + to_aes_nonce(&pn), + &mut assembled_packet, + )?; SessionEvent::Data(assembled_packet) } PACKET_TYPE_HANDSHAKE_RESPONSE => { @@ -334,11 +345,12 @@ impl Context { }, )?; log!(app, X3IsAuthSentKeyConfirm(&session)); - Ok(ReceiveOk::Session(session, if should_warn_missing_ratchet { + let ret = if should_warn_missing_ratchet { SessionEvent::NewDowngradedSession } else { SessionEvent::NewSession - })) + }; + Ok(ReceiveOk::Session(session, ret)) } else { Ok(ReceiveOk::Unassociated) } @@ -478,7 +490,10 @@ impl Context { &mut self, mut app: App, mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, - ) -> i64 where App: ApplicationLayer { + ) -> i64 + where + App: ApplicationLayer, + { let ctx = &self.0; let sessions = ctx.sessions.borrow_mut(); let current_time = app.time(); diff --git a/src/crypto_impl/kyber1024.rs b/src/crypto_impl/kyber1024.rs index 57cd5e7..6c2b1e8 100644 --- a/src/crypto_impl/kyber1024.rs +++ b/src/crypto_impl/kyber1024.rs @@ -5,8 +5,8 @@ use crate::crypto::*; /// A wrapper for a buffer the size of a pqc_kyber secret key. /// The crate `pqc_kyber` is low level and operates directly on buffers of bytes. -pub type RustKyber1024PrivateKey = Zeroizing<[u8; pqc_kyber::KYBER_SECRETKEYBYTES]>; -impl Kyber1024PrivateKey for RustKyber1024PrivateKey { +pub type Kyber1024CratePrivateKey = Zeroizing<[u8; pqc_kyber::KYBER_SECRETKEYBYTES]>; +impl Kyber1024PrivateKey for Kyber1024CratePrivateKey { fn generate(rng: &mut Rng) -> (Self, [u8; KYBER_PUBLIC_KEY_SIZE]) { let keypair = pqc_kyber::keypair(rng); (Zeroizing::new(keypair.secret), keypair.public) diff --git a/src/fragmentation.rs b/src/fragmentation.rs index c028295..2c303ec 100644 --- a/src/fragmentation.rs +++ b/src/fragmentation.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use zeroize::Zeroizing; -use crate::application::{CryptoLayer, ApplicationLayer}; +use crate::application::{ApplicationLayer, CryptoLayer}; use crate::crypto::{Aes256Prp, AES_256_KEY_SIZE}; use crate::proto::*; use crate::result::{byzantine_fault, ReceiveError}; diff --git a/src/zeta.rs b/src/zeta.rs index 7e8a25c..11df311 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -7,7 +7,7 @@ use std::sync::{Arc, Weak}; use rand_core::RngCore; use zeroize::Zeroizing; -use crate::application::{ApplicationLayer, RatchetState, RatchetStates, RatchetUpdate, CryptoLayer}; +use crate::application::{ApplicationLayer, CryptoLayer, RatchetState, RatchetStates, RatchetUpdate}; use crate::challenge::{gen_null_response, respond_to_challenge_in_place}; use crate::context::{log, ContextInner, SessionMap}; use crate::crypto::*; @@ -700,8 +700,8 @@ pub(crate) fn received_x3_trans( if !noise.decrypt_and_hash_in_place(to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 1), &mut x3[i..j], tag) { return Err(byzantine_fault!(FailedAuth, true)); } - let s_remote = - ::PublicKey::from_bytes((&x3[i..j]).try_into().unwrap()).ok_or(byzantine_fault!(FailedAuth, true))?; + let s_remote = ::PublicKey::from_bytes((&x3[i..j]).try_into().unwrap()) + .ok_or(byzantine_fault!(FailedAuth, true))?; i = k; // Process message pattern 3 se token. noise @@ -888,7 +888,10 @@ pub(crate) fn received_c1_trans( zeta.ratchet_state2 = None; zeta.key_index ^= true; let r = rng.borrow_mut().next_u64() % ::SETTINGS.rekey_time_max_jitter; - zeta.timeout_timer = app.time() + ::SETTINGS.rekey_after_time.saturating_sub(r) as i64; + zeta.timeout_timer = app.time() + + ::SETTINGS + .rekey_after_time + .saturating_sub(r) as i64; zeta.resend_timer = i64::MAX; zeta.beta = ZetaAutomata::S2; } @@ -925,7 +928,13 @@ pub(crate) fn received_c2_trans( } let tag = c2[..].try_into().unwrap(); - if !::Aead::decrypt_in_place(zeta.key_ref(false).recv.kek.as_ref().unwrap(), &n, None, &mut [], tag) { + if !::Aead::decrypt_in_place( + zeta.key_ref(false).recv.kek.as_ref().unwrap(), + &n, + None, + &mut [], + tag, + ) { return Err(byzantine_fault!(FailedAuth, true)); } let (_, c) = from_nonce(&n); @@ -934,7 +943,10 @@ pub(crate) fn received_c2_trans( } let r = rng.borrow_mut().next_u64() % ::SETTINGS.rekey_time_max_jitter; - zeta.timeout_timer = app.time() + ::SETTINGS.rekey_after_time.saturating_sub(r) as i64; + zeta.timeout_timer = app.time() + + ::SETTINGS + .rekey_after_time + .saturating_sub(r) as i64; zeta.resend_timer = i64::MAX; zeta.beta = ZetaAutomata::S2; Ok(()) @@ -957,7 +969,13 @@ pub(crate) fn received_d_trans( } let tag = d[..].try_into().unwrap(); - if !::Aead::decrypt_in_place(zeta.key_ref(true).recv.kek.as_ref().unwrap(), &n, None, &mut [], tag) { + if !::Aead::decrypt_in_place( + zeta.key_ref(true).recv.kek.as_ref().unwrap(), + &n, + None, + &mut [], + tag, + ) { return Err(byzantine_fault!(FailedAuth, true)); } let (_, c) = from_nonce(&n);