From 0c91b75bbdfc2585f83e9f87b64da20875a40a29 Mon Sep 17 00:00:00 2001 From: Sean OMeara Date: Wed, 18 Jan 2023 16:27:25 +0100 Subject: [PATCH] silence compiler warnings about _unused_variables (#1852) Tetanus noise xk (#1881) * Noise XK work in progress. * A whole lot more Noise_XK work... exchange almost done. * Delete a bunch of commented out old Noise_IK code. * Add back in send() and a few other things to Noise_XK ZSSP. * Some p384 experiment in attic * A ton of ZSSP work, and put MPL on ZSSP. * updated kbkdf512 to use the modern nist standard * Parameterize KBKDF on resulting output key size the way NIST likes. * updated variable comment * Make the label a const parameter on kbkdf. * updated variable comment * Add MPL to utils and other stuff. * layout tweak * Some more ZSSP work and a VDF we may use. * warning removal * More ZSSP work, add benchmarks for mimcvdf. * Almost ready to test... * Build fix. * Add automatic retransmission in the earliest stages of session init. * Just about ready to test... wow. * It opens a session. * ZSSP basically works... --------- Co-authored-by: mamoniot Warning removal. remove old docs Remove old tests from ZSSP, new test in main() can also be made into a unit test in the future. Add key_info() to get key information. Rekeying is now tested and works. Show key fingerprint. Factor out memory:: stuff, does not appear to have any real performance benefit. Rework defragmentation, and it now tolerates very poor link quality pretty well. Circuit breaker for incoming defrag queue, and ZSSP now works very well even under very poor network conditions. Format tweak. ZSSP API updates. Just a bit of final ZSSP cleanup before moving to another thing. --- controller/src/controller.rs | 10 +- controller/src/filedatabase.rs | 19 +- controller/src/postgresdatabase.rs | 37 +- crypto/benches/benchmark_crypto.rs | 32 +- crypto/src/aes.rs | 187 +- crypto/src/aes_gmac_siv/impl_openssl.rs | 24 +- crypto/src/hash.rs | 2 + crypto/src/lib.rs | 11 + crypto/src/mimcvdf.rs | 142 ++ crypto/src/p384.rs | 64 +- crypto/src/poly1305.rs | 12 +- crypto/src/salsa.rs | 10 +- crypto/src/secret.rs | 5 + network-hypervisor/src/vl1/endpoint.rs | 20 +- network-hypervisor/src/vl1/identity.rs | 78 +- network-hypervisor/src/vl1/inetaddress.rs | 11 +- network-hypervisor/src/vl1/mac.rs | 12 +- network-hypervisor/src/vl1/node.rs | 51 +- network-hypervisor/src/vl1/peer.rs | 65 +- network-hypervisor/src/vl2/networkconfig.rs | 23 +- rustfmt.toml | 2 +- service/src/main.rs | 9 +- utils/src/arrayvec.rs | 8 +- utils/src/blob.rs | 8 +- utils/src/buffer.rs | 8 +- utils/src/defer.rs | 12 +- utils/src/dictionary.rs | 14 +- utils/src/error.rs | 8 +- utils/src/exitcode.rs | 8 +- utils/src/gate.rs | 10 +- utils/src/gatherarray.rs | 14 +- utils/src/hex.rs | 8 +- utils/src/io.rs | 8 +- utils/src/json.rs | 14 +- utils/src/lib.rs | 13 +- utils/src/marshalable.rs | 8 +- utils/src/memory.rs | 8 +- utils/src/pool.rs | 8 +- utils/src/reaper.rs | 8 + utils/src/ringbuffer.rs | 8 +- utils/src/ringbuffermap.rs | 10 +- utils/src/sync.rs | 8 + utils/src/thing.rs | 8 +- utils/src/varint.rs | 8 +- vl1-service/src/constants.rs | 22 +- vl1-service/src/sys/udp.rs | 14 +- vl1-service/src/vl1service.rs | 3 +- vl1-service/src/vl1settings.rs | 3 +- zssp/Cargo.toml | 18 +- zssp/README.md | 6 - zssp/src/applicationlayer.rs | 115 +- zssp/src/constants.rs | 113 - zssp/src/error.rs | 55 +- zssp/src/fragged.rs | 107 + zssp/src/lib.rs | 16 +- zssp/src/main.rs | 283 +++ zssp/src/proto.rs | 219 ++ zssp/src/sessionid.rs | 27 +- zssp/src/tests.rs | 216 -- zssp/src/zssp.rs | 2504 ++++++++++--------- 60 files changed, 2757 insertions(+), 1997 deletions(-) create mode 100644 crypto/src/mimcvdf.rs delete mode 100644 zssp/src/constants.rs create mode 100644 zssp/src/fragged.rs create mode 100644 zssp/src/main.rs create mode 100644 zssp/src/proto.rs delete mode 100644 zssp/src/tests.rs diff --git a/controller/src/controller.rs b/controller/src/controller.rs index 32ef810d0..39556d07e 100644 --- a/controller/src/controller.rs +++ b/controller/src/controller.rs @@ -67,9 +67,7 @@ impl Controller { recently_authorized: RwLock::new(HashMap::new()), })) } else { - Err(Box::new(InvalidParameterError( - "local controller's identity not readable by database", - ))) + Err(Box::new(InvalidParameterError("local controller's identity not readable by database"))) } } @@ -238,8 +236,7 @@ impl Controller { if member.node_id != *m { if let Some(peer) = self.service.read().unwrap().upgrade().and_then(|s| s.node().peer(*m)) { revocations.clear(); - Revocation::new(member.network_id, time_clock, member.node_id, *m, &self.local_identity, false) - .map(|r| revocations.push(r)); + Revocation::new(member.network_id, time_clock, member.node_id, *m, &self.local_identity, false).map(|r| revocations.push(r)); self.send_revocations(&peer, &mut revocations); } } @@ -565,8 +562,7 @@ impl InnerProtocolLayer for Controller { }; // Launch handler as an async background task. - let (self2, source, source_remote_endpoint) = - (self.self_ref.upgrade().unwrap(), source.clone(), source_path.endpoint.clone()); + let (self2, source, source_remote_endpoint) = (self.self_ref.upgrade().unwrap(), source.clone(), source_path.endpoint.clone()); self.reaper.add( self.runtime.spawn(async move { let node_id = source.identity.address; diff --git a/controller/src/filedatabase.rs b/controller/src/filedatabase.rs index 6fb5d9e68..f4b5aeecc 100644 --- a/controller/src/filedatabase.rs +++ b/controller/src/filedatabase.rs @@ -50,8 +50,8 @@ impl FileDatabase { let db_weak = db_weak_tmp.clone(); let runtime2 = runtime.clone(); - let local_identity = load_node_identity(base_path.as_path()) - .ok_or(std::io::Error::new(std::io::ErrorKind::NotFound, "identity.secret not found"))?; + let local_identity = + load_node_identity(base_path.as_path()).ok_or(std::io::Error::new(std::io::ErrorKind::NotFound, "identity.secret not found"))?; let controller_address = local_identity.address; let db = Arc::new(Self { @@ -144,8 +144,7 @@ impl FileDatabase { .send(Change::NetworkChanged(old_network, new_network)); } (true, None) => { - let _ = - db.change_sender.send(Change::NetworkCreated(new_network)); + let _ = db.change_sender.send(Change::NetworkCreated(new_network)); } _ => {} } @@ -155,13 +154,11 @@ impl FileDatabase { if let Ok(Some(new_member)) = Self::load_object::(changed).await { match db.cache.on_member_updated(new_member.clone()) { (true, Some(old_member)) => { - let _ = db - .change_sender - .send(Change::MemberChanged(old_member, new_member)); + let _ = + db.change_sender.send(Change::MemberChanged(old_member, new_member)); } (true, None) => { - let _ = - db.change_sender.send(Change::MemberCreated(new_member)); + let _ = db.change_sender.send(Change::MemberCreated(new_member)); } _ => {} } @@ -315,9 +312,7 @@ impl Database for FileDatabase { if ent.file_type().await.map_or(false, |t| t.is_file() || t.is_symlink()) { let osname = ent.file_name(); let name = osname.to_string_lossy(); - if name.len() == (zerotier_network_hypervisor::protocol::ADDRESS_SIZE_STRING + 6) - && name.starts_with("M") - && name.ends_with(".yaml") + if name.len() == (zerotier_network_hypervisor::protocol::ADDRESS_SIZE_STRING + 6) && name.starts_with("M") && name.ends_with(".yaml") { if let Ok(member_address) = u64::from_str_radix(&name[1..11], 16) { if let Some(member_address) = Address::from_u64(member_address) { diff --git a/controller/src/postgresdatabase.rs b/controller/src/postgresdatabase.rs index cc7198ed9..379b44a6e 100644 --- a/controller/src/postgresdatabase.rs +++ b/controller/src/postgresdatabase.rs @@ -88,18 +88,13 @@ impl PostgresConnection { let (client, connection) = tokio_postgres::connect(postgres_path, tokio_postgres::NoTls).await?; Ok(Box::new(Self { s_list_networks: client - .prepare_typed( - "SELECT id FROM ztc_network WHERE controller_id = $1 AND deleted = false", - &[Type::TEXT], - ) + .prepare_typed("SELECT id FROM ztc_network WHERE controller_id = $1 AND deleted = false", &[Type::TEXT]) .await?, s_list_members: client .prepare_typed("SELECT id FROM ztc_member WHERE network_id = $1 AND deleted = false", &[Type::TEXT]) .await?, s_get_network: client.prepare_typed(GET_NETWORK_SQL, &[Type::TEXT]).await?, - s_get_network_members_with_capabilities: client - .prepare_typed(GET_NETWORK_MEMBERS_WITH_CAPABILITIES_SQL, &[Type::TEXT]) - .await?, + s_get_network_members_with_capabilities: client.prepare_typed(GET_NETWORK_MEMBERS_WITH_CAPABILITIES_SQL, &[Type::TEXT]).await?, client, connection_task: runtime.spawn(async move { if let Err(e) = connection.await { @@ -144,12 +139,7 @@ pub struct PostgresDatabase { } impl PostgresDatabase { - pub async fn new( - runtime: Handle, - postgres_path: String, - num_connections: usize, - local_identity: Valid, - ) -> Result, Error> { + pub async fn new(runtime: Handle, postgres_path: String, num_connections: usize, local_identity: Valid) -> Result, Error> { assert!(num_connections > 0); let (sender, _) = channel(4096); let mut connections = Vec::with_capacity(num_connections); @@ -193,7 +183,7 @@ impl VL1DataStorage for PostgresDatabase { Some(self.local_identity.clone()) } - fn save_node_identity(&self, id: &Valid) -> bool { + fn save_node_identity(&self, _id: &Valid) -> bool { panic!("local identity saving not supported by PostgresDatabase") } } @@ -217,11 +207,7 @@ impl Database for PostgresDatabase { let c = self.get_connection().await?; let network_id_string = id.to_string(); if let Some(r) = c.client.query_opt(&c.s_get_network, &[&network_id_string]).await? { - if let Ok(with_caps) = c - .client - .query(&c.s_get_network_members_with_capabilities, &[&network_id_string]) - .await - { + if let Ok(with_caps) = c.client.query(&c.s_get_network_members_with_capabilities, &[&network_id_string]).await { (r, with_caps) } else { (r, Vec::new()) @@ -341,10 +327,7 @@ impl Database for PostgresDatabase { if let Ok(member_id) = Address::from_str(wc.get(0)) { if let Ok(cap_ids) = serde_json::from_str::>(wc.get(1)) { for cap_id in cap_ids.iter() { - members_by_cap - .entry(*cap_id) - .or_insert_with(|| Vec::with_capacity(4)) - .push(member_id); + members_by_cap.entry(*cap_id).or_insert_with(|| Vec::with_capacity(4)).push(member_id); } } } @@ -389,7 +372,7 @@ impl Database for PostgresDatabase { })) } - async fn save_network(&self, obj: Network, generate_change_notification: bool) -> Result<(), Error> { + async fn save_network(&self, _obj: Network, _generate_change_notification: bool) -> Result<(), Error> { todo!() } @@ -405,11 +388,11 @@ impl Database for PostgresDatabase { Ok(r) } - async fn get_member(&self, network_id: NetworkId, node_id: Address) -> Result, Error> { + async fn get_member(&self, _network_id: NetworkId, _node_id: Address) -> Result, Error> { todo!() } - async fn save_member(&self, obj: Member, generate_change_notification: bool) -> Result<(), Error> { + async fn save_member(&self, _obj: Member, _generate_change_notification: bool) -> Result<(), Error> { todo!() } @@ -443,7 +426,7 @@ impl Database for PostgresDatabase { return Ok(false); } - async fn log_request(&self, obj: RequestLogItem) -> Result<(), Error> { + async fn log_request(&self, _obj: RequestLogItem) -> Result<(), Error> { todo!() } } diff --git a/crypto/benches/benchmark_crypto.rs b/crypto/benches/benchmark_crypto.rs index ee2a7efb7..2157322aa 100644 --- a/crypto/benches/benchmark_crypto.rs +++ b/crypto/benches/benchmark_crypto.rs @@ -1,34 +1,38 @@ use criterion::{criterion_group, criterion_main, Criterion}; use std::time::Duration; +use zerotier_crypto::mimcvdf; use zerotier_crypto::p384::*; -use zerotier_crypto::random; use zerotier_crypto::x25519::*; pub fn criterion_benchmark(c: &mut Criterion) { + let mut group = c.benchmark_group("cryptography"); + + let mut input = 1; + let mut proof = 0; + group.bench_function("mimcvdf::delay(1000)", |b| { + b.iter(|| { + input += 1; + proof = mimcvdf::delay(input, 1000); + }) + }); + group.bench_function("mimcvdf::verify(1000)", |b| { + b.iter(|| { + assert!(mimcvdf::verify(proof, input, 1000)); + }) + }); + let p384_a = P384KeyPair::generate(); let p384_b = P384KeyPair::generate(); - //let kyber_a = pqc_kyber::keypair(&mut random::SecureRandom::default()); - //let kyber_encap = pqc_kyber::encapsulate(&kyber_a.public, &mut random::SecureRandom::default()).unwrap(); - let x25519_a = X25519KeyPair::generate(); let x25519_b = X25519KeyPair::generate(); let x25519_b_pub = x25519_b.public_bytes(); - let mut group = c.benchmark_group("cryptography"); group.measurement_time(Duration::new(10, 0)); - group.bench_function("ecdhp384", |b| { - b.iter(|| p384_a.agree(p384_b.public_key()).expect("ecdhp384 failed")) - }); + group.bench_function("ecdhp384", |b| b.iter(|| p384_a.agree(p384_b.public_key()).expect("ecdhp384 failed"))); group.bench_function("ecdhx25519", |b| b.iter(|| x25519_a.agree(&x25519_b_pub))); - //group.bench_function("kyber_encapsulate", |b| { - // b.iter(|| pqc_kyber::encapsulate(&kyber_a.public, &mut random::SecureRandom::default()).expect("kyber encapsulate failed")) - //}); - //group.bench_function("kyber_decapsulate", |b| { - // b.iter(|| pqc_kyber::decapsulate(&kyber_encap.0, &kyber_a.secret).expect("kyber decapsulate failed")) - //}); group.finish(); } diff --git a/crypto/src/aes.rs b/crypto/src/aes.rs index 014d241ab..54be9e016 100644 --- a/crypto/src/aes.rs +++ b/crypto/src/aes.rs @@ -46,7 +46,7 @@ mod fruit_flavored { data_out_len: usize, data_out_written: *mut usize, ) -> i32; - //fn CCCryptorReset(cryptor_ref: *mut c_void, iv: *const c_void) -> i32; + fn CCCryptorReset(cryptor_ref: *mut c_void, iv: *const c_void) -> i32; fn CCCryptorRelease(cryptor_ref: *mut c_void) -> i32; fn CCCryptorGCMSetIV(cryptor_ref: *mut c_void, iv: *const c_void, iv_len: usize) -> i32; fn CCCryptorGCMAddAAD(cryptor_ref: *mut c_void, aad: *const c_void, len: usize) -> i32; @@ -135,14 +135,7 @@ mod fruit_flavored { assert_eq!(data.len(), 16); unsafe { let mut data_out_written = 0; - CCCryptorUpdate( - self.0, - data.as_ptr().cast(), - 16, - data.as_mut_ptr().cast(), - 16, - &mut data_out_written, - ); + CCCryptorUpdate(self.0, data.as_ptr().cast(), 16, data.as_mut_ptr().cast(), 16, &mut data_out_written); } } @@ -168,14 +161,7 @@ mod fruit_flavored { assert_eq!(data.len(), 16); unsafe { let mut data_out_written = 0; - CCCryptorUpdate( - self.1, - data.as_ptr().cast(), - 16, - data.as_mut_ptr().cast(), - 16, - &mut data_out_written, - ); + CCCryptorUpdate(self.1, data.as_ptr().cast(), 16, data.as_mut_ptr().cast(), 16, &mut data_out_written); } } } @@ -183,6 +169,101 @@ mod fruit_flavored { unsafe impl Send for Aes {} unsafe impl Sync for Aes {} + pub struct AesCtr(*mut c_void); + + impl Drop for AesCtr { + #[inline(always)] + fn drop(&mut self) { + unsafe { CCCryptorRelease(self.0) }; + } + } + + impl AesCtr { + /// Construct a new AES-CTR cipher. + /// Key must be 16, 24, or 32 bytes in length or a panic will occur. + pub fn new(k: &[u8]) -> Self { + if k.len() != 32 && k.len() != 24 && k.len() != 16 { + panic!("AES supports 128, 192, or 256 bits keys"); + } + unsafe { + let mut ptr: *mut c_void = null_mut(); + let result = CCCryptorCreateWithMode( + kCCEncrypt, + kCCModeCTR, + kCCAlgorithmAES, + 0, + [0_u64; 2].as_ptr().cast(), + k.as_ptr().cast(), + k.len(), + null(), + 0, + 0, + 0, + &mut ptr, + ); + if result != 0 { + panic!("CCCryptorCreateWithMode for CTR mode returned {}", result); + } + AesCtr(ptr) + } + } + + /// Initialize AES-CTR for encryption or decryption with the given IV. + /// If it's already been used, this also resets the cipher. There is no separate reset. + pub fn reset_set_iv(&mut self, iv: &[u8]) { + unsafe { + if iv.len() == 16 { + if CCCryptorReset(self.0, iv.as_ptr().cast()) != 0 { + panic!("CCCryptorReset for CTR mode failed (old MacOS bug)"); + } + } else if iv.len() < 16 { + let mut iv2 = [0_u8; 16]; + iv2[0..iv.len()].copy_from_slice(iv); + if CCCryptorReset(self.0, iv2.as_ptr().cast()) != 0 { + panic!("CCCryptorReset for CTR mode failed (old MacOS bug)"); + } + } else { + panic!("CTR IV must be less than or equal to 16 bytes in length"); + } + } + } + + /// Encrypt or decrypt (same operation with CTR mode) + #[inline(always)] + pub fn crypt(&mut self, input: &[u8], output: &mut [u8]) { + unsafe { + assert!(output.len() >= input.len()); + let mut data_out_written: usize = 0; + CCCryptorUpdate( + self.0, + input.as_ptr().cast(), + input.len(), + output.as_mut_ptr().cast(), + output.len(), + &mut data_out_written, + ); + } + } + + /// Encrypt or decrypt in place (same operation with CTR mode) + #[inline(always)] + pub fn crypt_in_place(&mut self, data: &mut [u8]) { + unsafe { + let mut data_out_written: usize = 0; + CCCryptorUpdate( + self.0, + data.as_ptr().cast(), + data.len(), + data.as_mut_ptr().cast(), + data.len(), + &mut data_out_written, + ); + } + } + } + + unsafe impl Send for AesCtr {} + pub struct AesGcm(*mut c_void, bool); impl Drop for AesGcm { @@ -262,15 +343,9 @@ mod fruit_flavored { pub fn crypt_in_place(&mut self, data: &mut [u8]) { unsafe { if self.1 { - assert_eq!( - CCCryptorGCMEncrypt(self.0, data.as_ptr().cast(), data.len(), data.as_mut_ptr().cast()), - 0 - ); + assert_eq!(CCCryptorGCMEncrypt(self.0, data.as_ptr().cast(), data.len(), data.as_mut_ptr().cast()), 0); } else { - assert_eq!( - CCCryptorGCMDecrypt(self.0, data.as_ptr().cast(), data.len(), data.as_mut_ptr().cast()), - 0 - ); + assert_eq!(CCCryptorGCMDecrypt(self.0, data.as_ptr().cast(), data.len(), data.as_mut_ptr().cast()), 0); } } } @@ -307,6 +382,17 @@ mod openssl_aes { use std::cell::UnsafeCell; use std::mem::MaybeUninit; + fn aes_ctr_by_key_size(ks: usize) -> Cipher { + match ks { + 16 => Cipher::aes_128_ctr(), + 24 => Cipher::aes_192_ctr(), + 32 => Cipher::aes_256_ctr(), + _ => { + panic!("AES supports 128, 192, or 256 bits keys"); + } + } + } + fn aes_gcm_by_key_size(ks: usize) -> Cipher { match ks { 16 => Cipher::aes_128_gcm(), @@ -390,6 +476,53 @@ mod openssl_aes { unsafe impl Send for Aes {} unsafe impl Sync for Aes {} + pub struct AesCtr(Secret<32>, usize, Option); + + impl AesCtr { + /// Construct a new AES-CTR cipher. + /// Key must be 16, 24, or 32 bytes in length or a panic will occur. + #[inline(always)] + pub fn new(k: &[u8]) -> Self { + let mut s: Secret<32> = Secret::default(); + match k.len() { + 16 | 24 | 32 => { + s.0[..k.len()].copy_from_slice(k); + Self(s, k.len(), None) + } + _ => { + panic!("AES supports 128, 192, or 256 bits keys"); + } + } + } + + /// Initialize AES-CTR for encryption or decryption with the given IV. + /// If it's already been used, this also resets the cipher. There is no separate reset. + #[inline(always)] + pub fn reset_set_iv(&mut self, iv: &[u8]) { + let mut c = Crypter::new(aes_ctr_by_key_size(self.1), Mode::Encrypt, &self.0 .0[..self.1], Some(iv)).unwrap(); + c.pad(false); + let _ = self.2.replace(c); + } + + /// Encrypt or decrypt (same operation with CTR mode) + #[inline(always)] + pub fn crypt(&mut self, input: &[u8], output: &mut [u8]) { + let _ = self.2.as_mut().unwrap().update(input, output); + } + + /// Encrypt or decrypt in place (same operation with CTR mode) + #[inline(always)] + pub fn crypt_in_place(&mut self, data: &mut [u8]) { + let _ = self + .2 + .as_mut() + .unwrap() + .update(unsafe { &*std::slice::from_raw_parts(data.as_ptr(), data.len()) }, data); + } + } + + unsafe impl Send for AesCtr {} + pub struct AesGcm(Secret<32>, usize, CipherCtx, bool); impl AesGcm { @@ -479,10 +612,10 @@ mod openssl_aes { } #[cfg(target_os = "macos")] -pub use fruit_flavored::{Aes, AesGcm}; +pub use fruit_flavored::{Aes, AesCtr, AesGcm}; #[cfg(not(target_os = "macos"))] -pub use openssl_aes::{Aes, AesGcm}; +pub use openssl_aes::{Aes, AesCtr, AesGcm}; #[cfg(test)] mod tests { diff --git a/crypto/src/aes_gmac_siv/impl_openssl.rs b/crypto/src/aes_gmac_siv/impl_openssl.rs index edd856fd9..7615aafd7 100644 --- a/crypto/src/aes_gmac_siv/impl_openssl.rs +++ b/crypto/src/aes_gmac_siv/impl_openssl.rs @@ -178,15 +178,9 @@ impl AesGmacSiv { self.tmp.copy_from_slice(&tag_tmp[0..16]); self.tmp[12] &= 0x7f; - let _ = self.ctr.replace( - Crypter::new( - aes_ctr_by_key_size(self.k1.len()), - Mode::Encrypt, - self.k1.as_slice(), - Some(&self.tmp), - ) - .unwrap(), - ); + let _ = self + .ctr + .replace(Crypter::new(aes_ctr_by_key_size(self.k1.len()), Mode::Encrypt, self.k1.as_slice(), Some(&self.tmp)).unwrap()); } /// Feed plaintext for second pass and write ciphertext to supplied buffer. @@ -219,15 +213,9 @@ impl AesGmacSiv { pub fn decrypt_init(&mut self, tag: &[u8]) { self.tmp.copy_from_slice(tag); self.tmp[12] &= 0x7f; - let _ = self.ctr.replace( - Crypter::new( - aes_ctr_by_key_size(self.k1.len()), - Mode::Decrypt, - self.k1.as_slice(), - Some(&self.tmp), - ) - .unwrap(), - ); + let _ = self + .ctr + .replace(Crypter::new(aes_ctr_by_key_size(self.k1.len()), Mode::Decrypt, self.k1.as_slice(), Some(&self.tmp)).unwrap()); let mut tag_tmp = [0_u8; 32]; let mut ecb = Crypter::new(aes_ecb_by_key_size(self.k1.len()), Mode::Decrypt, self.k1.as_slice(), None).unwrap(); diff --git a/crypto/src/hash.rs b/crypto/src/hash.rs index 85131f54e..d148517c2 100644 --- a/crypto/src/hash.rs +++ b/crypto/src/hash.rs @@ -7,6 +7,8 @@ use std::ptr::null; pub const SHA512_HASH_SIZE: usize = 64; pub const SHA384_HASH_SIZE: usize = 48; +pub const HMAC_SHA512_SIZE: usize = 64; +pub const HMAC_SHA384_SIZE: usize = 48; pub struct SHA512(Option); diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs index 94ab7ff7f..685bccde7 100644 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -3,6 +3,7 @@ pub mod aes; pub mod aes_gmac_siv; pub mod hash; +pub mod mimcvdf; pub mod p384; pub mod poly1305; pub mod random; @@ -27,3 +28,13 @@ pub fn secure_eq + ?Sized, B: AsRef<[u8]> + ?Sized>(a: &A, b: &B) false } } + +extern "C" { + fn OPENSSL_cleanse(ptr: *mut std::ffi::c_void, len: usize); +} + +/// Destroy the contents of some memory +#[inline(always)] +pub fn burn(b: &mut [u8]) { + unsafe { OPENSSL_cleanse(b.as_mut_ptr().cast(), b.len()) }; +} diff --git a/crypto/src/mimcvdf.rs b/crypto/src/mimcvdf.rs new file mode 100644 index 000000000..030dbff69 --- /dev/null +++ b/crypto/src/mimcvdf.rs @@ -0,0 +1,142 @@ +/* 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/ + */ + +/* + * MIMC is a cipher originally designed for use with STARK and SNARK proofs. It's based + * on modular multiplication and exponentiation instead of the usual bit twiddling or ARX + * operations that underpin more common hash algorithms. + * + * It's useful as a verifiable delay function because it can be computed in both directions with + * one direction taking orders of magnitude longer than the other. The "backward" direction is + * used as the delay function as it requires modular exponentiation which is inherently more + * compute intensive. The "forward" direction simply requires modular cubing which is two modular + * multiplications and is much faster. + * + * It's a nice VDF because it's incredibly simple with a tiny code footprint. Most other VDFs + * involve RSA group operations or zero knowledge proofs. + * + * This is used for anti-DOS and anti-spamming delay functions. It's not used for anything + * really "cryptographically hard," and if it were broken cryptographically it would still be + * useful as a VDF as long as the break didn't yield a significantly faster way of computing a + * delay proof than the straightforward iterative way implemented here. + * + * Here are two references on MIMC with the first being the original paper and the second being + * a blog post describing its use as a VDF. + * + * https://eprint.iacr.org/2016/492.pdf + * https://vitalik.ca/general/2018/07/21/starks_part_3.html + */ + +// p = 2^127 - 39, the largest 127-bit prime of the form 6k + 5 +const PRIME: u128 = 170141183460469231731687303715884105689; + +// (2p - 1) / 3 +const PRIME_2P_MINUS_1_DIV_3: u128 = 113427455640312821154458202477256070459; + +// Randomly generated round constants, each modulo PRIME. +const K_COUNT_MASK: usize = 31; +const K: [u128; 32] = [ + 0x1fdd07a761b611bb1ab9419a70599a7c, + 0x23056b05d5c6b925e333d7418047650a, + 0x77a638f9b437a307f8866fbd2672c705, + 0x60213dab83bab91d1c310bd87e9da332, + 0xf56bc883301ab373179e46b098b7a7, + 0x7914a0dbd2f971344173b350c28a838, + 0x44bb64af5e446e6ebdc068d10d318f26, + 0x1bca1921fd328bb725ae0cbcbc20a263, + 0xafa963242f5216a7da1cd5328b23659, + 0x7fe17c43782b883a63ee0a790e0b2b77, + 0x23bb62abf728bf453200ee528f902c33, + 0x75ec0c055be14955db6878567e3c0465, + 0x7902bb57876e0b08b4de02a66755e5d7, + 0xe5d7094f37b615f5a1e1594b0390de8, + 0x12d4ddee90653a26f5de63ff4651f2d, + 0xce4a15bc35633b5ed8bcae2c93d739c, + 0x23f25b935e52df87255db8c608ef9ab4, + 0x611a08d7464fb984c98104d77f1609a7, + 0x7aa825876a7f6acde5efa57992da9c43, + 0x2be9686f630fa28a0a0e1081a59755b4, + 0x50060dac9ac4656ba3f8ee7592f4e28a, + 0x4113abff6f5bb303eac2ca809d4d529d, + 0x2af9d01d4e753feb5834c14ca0543397, + 0x73c2d764691ced2b823dda887e22ae85, + 0x5b53dcd4750ff888dca2497cec4dacb7, + 0x5d8984a52c2d8f3cc9bcf61ef29f8a1, + 0x588d8cc99533d649aabb5f0f552140e, + 0x4dae04985fde8c8464ba08aaa7d8761e, + 0x53f0c4740b8c3bda3fc05109b9a2b71, + 0x3e918c88a6795e3bf840e0b74d91b9d7, + 0x1dbcb30d724f11200aebb1dff87def91, + 0x6086b0af0e1e68558170239d23be9780, +]; + +fn mulmod(mut a: u128, mut b: u128) -> u128 { + let mut res: u128 = 0; + a %= M; + loop { + if (b & 1) != 0 { + res = res.wrapping_add(a) % M; + } + b = b.wrapping_shr(1); + if b != 0 { + a = a.wrapping_shl(1) % M; + } else { + return res; + } + } +} + +#[inline(always)] +fn powmod(mut base: u128, mut exp: u128) -> u128 { + let mut res: u128 = 1; + loop { + if (exp & 1) != 0 { + res = mulmod::(base, res); + } + exp = exp.wrapping_shr(1); + if exp != 0 { + base = mulmod::(base, base); + } else { + return res; + } + } +} + +/// Compute MIMC for the given number of iterations and return a proof that can be checked much more quickly. +pub fn delay(mut input: u128, rounds: usize) -> u128 { + debug_assert!(rounds > 0); + input %= PRIME; + for r in 1..(rounds + 1) { + input = powmod::(input ^ K[(rounds - r) & K_COUNT_MASK], PRIME_2P_MINUS_1_DIV_3); + } + input +} + +/// Quickly verify the result of delay() given the returned proof, original input, and original number of rounds. +pub fn verify(mut proof: u128, original_input: u128, rounds: usize) -> bool { + debug_assert!(rounds > 0); + for r in 0..rounds { + proof = mulmod::(proof, mulmod::(proof, proof)) ^ K[r & K_COUNT_MASK]; + } + proof == (original_input % PRIME) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn delay_and_verify() { + for i in 1..5 { + let input = (crate::random::xorshift64_random() as u128).wrapping_mul(crate::random::xorshift64_random() as u128); + let proof = delay(input, i * 3); + //println!("{}", proof); + assert!(verify(proof, input, i * 3)); + } + } +} diff --git a/crypto/src/p384.rs b/crypto/src/p384.rs index 4baea80d9..28f4a0066 100644 --- a/crypto/src/p384.rs +++ b/crypto/src/p384.rs @@ -55,9 +55,7 @@ mod openssl_based { impl P384PublicKey { fn new_from_point(key: &EcPointRef) -> Self { let mut bnc = BigNumContext::new().unwrap(); - let kb = key - .to_bytes(GROUP_P384.as_ref(), PointConversionForm::COMPRESSED, &mut bnc) - .unwrap(); + let kb = key.to_bytes(GROUP_P384.as_ref(), PointConversionForm::COMPRESSED, &mut bnc).unwrap(); let mut bytes = [0_u8; 49]; bytes[(49 - kb.len())..].copy_from_slice(kb.as_slice()); Self { @@ -599,17 +597,12 @@ mod builtin { let mut i: uint = 0; /* p = c0 */ vli_clear(l_tmp.as_mut_ptr()); vli_clear(l_tmp.as_mut_ptr().offset((48 as libc::c_int / 8 as libc::c_int) as isize)); - omega_mult( - l_tmp.as_mut_ptr(), - p_product.offset((48 as libc::c_int / 8 as libc::c_int) as isize), - ); + omega_mult(l_tmp.as_mut_ptr(), p_product.offset((48 as libc::c_int / 8 as libc::c_int) as isize)); vli_clear(p_product.offset((48 as libc::c_int / 8 as libc::c_int) as isize)); /* (c1, c0) = c0 + w * c1 */ i = 0 as libc::c_int as uint; while i < (48 as libc::c_int / 8 as libc::c_int + 3 as libc::c_int) as libc::c_uint { - let mut l_sum: uint64_t = (*p_product.offset(i as isize)) - .wrapping_add(l_tmp[i as usize]) - .wrapping_add(l_carry); + let mut l_sum: uint64_t = (*p_product.offset(i as isize)).wrapping_add(l_tmp[i as usize]).wrapping_add(l_carry); if l_sum != *p_product.offset(i as isize) { l_carry = (l_sum < *p_product.offset(i as isize)) as libc::c_int as uint64_t } @@ -855,12 +848,7 @@ mod builtin { vli_set(X1, t7.as_mut_ptr()); } - unsafe fn EccPoint_mult( - mut p_result: *mut EccPoint, - mut p_point: *mut EccPoint, - mut p_scalar: *mut uint64_t, - mut p_initialZ: *mut uint64_t, - ) { + unsafe fn EccPoint_mult(mut p_result: *mut EccPoint, mut p_point: *mut EccPoint, mut p_scalar: *mut uint64_t, mut p_initialZ: *mut uint64_t) { /* R0 and R1 */ let mut Rx: [[uint64_t; 6]; 2] = std::mem::MaybeUninit::uninit().assume_init(); let mut Ry: [[uint64_t; 6]; 2] = std::mem::MaybeUninit::uninit().assume_init(); @@ -1039,20 +1027,12 @@ mod builtin { } } ecc_native2bytes(p_privateKey, l_private.as_mut_ptr() as *const uint64_t); - ecc_native2bytes( - p_publicKey.offset(1 as libc::c_int as isize), - l_public.x.as_mut_ptr() as *const uint64_t, - ); - *p_publicKey.offset(0 as libc::c_int as isize) = (2 as libc::c_int as libc::c_ulong) - .wrapping_add(l_public.y[0 as libc::c_int as usize] & 0x1 as libc::c_int as libc::c_ulong) - as uint8_t; + ecc_native2bytes(p_publicKey.offset(1 as libc::c_int as isize), l_public.x.as_mut_ptr() as *const uint64_t); + *p_publicKey.offset(0 as libc::c_int as isize) = + (2 as libc::c_int as libc::c_ulong).wrapping_add(l_public.y[0 as libc::c_int as usize] & 0x1 as libc::c_int as libc::c_ulong) as uint8_t; return 1 as libc::c_int; } - pub unsafe fn ecdh_shared_secret( - mut p_publicKey: *const uint8_t, - mut p_privateKey: *const uint8_t, - mut p_secret: *mut uint8_t, - ) -> libc::c_int { + pub unsafe fn ecdh_shared_secret(mut p_publicKey: *const uint8_t, mut p_privateKey: *const uint8_t, mut p_secret: *mut uint8_t) -> libc::c_int { let mut l_public: EccPoint = std::mem::MaybeUninit::uninit().assume_init(); let mut l_private: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); let mut l_random: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); @@ -1079,8 +1059,7 @@ mod builtin { vli_mult(l_product.as_mut_ptr(), p_left, p_right); l_productBits = vli_numBits(l_product.as_mut_ptr().offset((48 as libc::c_int / 8 as libc::c_int) as isize)); if l_productBits != 0 { - l_productBits = (l_productBits as libc::c_uint) - .wrapping_add((48 as libc::c_int / 8 as libc::c_int * 64 as libc::c_int) as libc::c_uint) + l_productBits = (l_productBits as libc::c_uint).wrapping_add((48 as libc::c_int / 8 as libc::c_int * 64 as libc::c_int) as libc::c_uint) as uint as uint } else { l_productBits = vli_numBits(l_product.as_mut_ptr()) @@ -1094,12 +1073,8 @@ mod builtin { power of two possible while still resulting in a number less than p_left. */ vli_clear(l_modMultiple.as_mut_ptr()); vli_clear(l_modMultiple.as_mut_ptr().offset((48 as libc::c_int / 8 as libc::c_int) as isize)); - l_digitShift = l_productBits - .wrapping_sub(l_modBits) - .wrapping_div(64 as libc::c_int as libc::c_uint); - l_bitShift = l_productBits - .wrapping_sub(l_modBits) - .wrapping_rem(64 as libc::c_int as libc::c_uint); + l_digitShift = l_productBits.wrapping_sub(l_modBits).wrapping_div(64 as libc::c_int as libc::c_uint); + l_bitShift = l_productBits.wrapping_sub(l_modBits).wrapping_rem(64 as libc::c_int as libc::c_uint); if l_bitShift != 0 { l_modMultiple[l_digitShift.wrapping_add((48 as libc::c_int / 8 as libc::c_int) as libc::c_uint) as usize] = vli_lshift(l_modMultiple.as_mut_ptr().offset(l_digitShift as isize), p_mod, l_bitShift) @@ -1186,11 +1161,7 @@ mod builtin { ecc_native2bytes(p_signature.offset(48 as libc::c_int as isize), l_s.as_mut_ptr() as *const uint64_t); return 1 as libc::c_int; } - pub unsafe fn ecdsa_verify( - mut p_publicKey: *const uint8_t, - mut p_hash: *const uint8_t, - mut p_signature: *const uint8_t, - ) -> libc::c_int { + pub unsafe fn ecdsa_verify(mut p_publicKey: *const uint8_t, mut p_hash: *const uint8_t, mut p_signature: *const uint8_t) -> libc::c_int { let mut u1: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); let mut u2: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); let mut z: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); @@ -1210,8 +1181,7 @@ mod builtin { /* r, s must not be 0. */ return 0 as libc::c_int; } - if vli_cmp(curve_n.as_mut_ptr(), l_r.as_mut_ptr()) != 1 as libc::c_int - || vli_cmp(curve_n.as_mut_ptr(), l_s.as_mut_ptr()) != 1 as libc::c_int + if vli_cmp(curve_n.as_mut_ptr(), l_r.as_mut_ptr()) != 1 as libc::c_int || vli_cmp(curve_n.as_mut_ptr(), l_s.as_mut_ptr()) != 1 as libc::c_int { /* r, s must be < n. */ return 0 as libc::c_int; @@ -1233,10 +1203,10 @@ mod builtin { /* Use Shamir's trick to calculate u1*G + u2*Q */ let mut l_points: [*mut EccPoint; 4] = [0 as *mut EccPoint, &mut curve_G, &mut l_public, &mut l_sum]; /* Z = x2 - x1 */ let mut l_numBits: uint = umax(vli_numBits(u1.as_mut_ptr()), vli_numBits(u2.as_mut_ptr())); /* Z = 1/Z */ - let mut l_point: *mut EccPoint = l_points[((vli_testBit(u1.as_mut_ptr(), l_numBits.wrapping_sub(1 as libc::c_int as libc::c_uint)) - != 0) as libc::c_int - | ((vli_testBit(u2.as_mut_ptr(), l_numBits.wrapping_sub(1 as libc::c_int as libc::c_uint)) != 0) as libc::c_int) - << 1 as libc::c_int) as usize]; + let mut l_point: *mut EccPoint = l_points[((vli_testBit(u1.as_mut_ptr(), l_numBits.wrapping_sub(1 as libc::c_int as libc::c_uint)) != 0) + as libc::c_int + | ((vli_testBit(u2.as_mut_ptr(), l_numBits.wrapping_sub(1 as libc::c_int as libc::c_uint)) != 0) as libc::c_int) << 1 as libc::c_int) + as usize]; vli_set(rx.as_mut_ptr(), (*l_point).x.as_mut_ptr()); vli_set(ry.as_mut_ptr(), (*l_point).y.as_mut_ptr()); vli_clear(z.as_mut_ptr()); diff --git a/crypto/src/poly1305.rs b/crypto/src/poly1305.rs index b34da874c..b49183ab0 100644 --- a/crypto/src/poly1305.rs +++ b/crypto/src/poly1305.rs @@ -20,12 +20,12 @@ mod tests { use crate::poly1305::*; const TV0_INPUT: [u8; 32] = [ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; const TV0_KEY: [u8; 32] = [ - 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x33, 0x32, 0x2d, 0x62, 0x79, 0x74, 0x65, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x66, 0x6f, - 0x72, 0x20, 0x50, 0x6f, 0x6c, 0x79, 0x31, 0x33, 0x30, 0x35, + 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x33, 0x32, 0x2d, 0x62, 0x79, 0x74, 0x65, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x66, 0x6f, 0x72, + 0x20, 0x50, 0x6f, 0x6c, 0x79, 0x31, 0x33, 0x30, 0x35, ]; const TV0_TAG: [u8; 16] = [ 0x49, 0xec, 0x78, 0x09, 0x0e, 0x48, 0x1e, 0xc6, 0xc2, 0x6b, 0x33, 0xb9, 0x1c, 0xcc, 0x03, 0x07, @@ -33,8 +33,8 @@ mod tests { const TV1_INPUT: [u8; 12] = [0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21]; const TV1_KEY: [u8; 32] = [ - 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x33, 0x32, 0x2d, 0x62, 0x79, 0x74, 0x65, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x66, 0x6f, - 0x72, 0x20, 0x50, 0x6f, 0x6c, 0x79, 0x31, 0x33, 0x30, 0x35, + 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x33, 0x32, 0x2d, 0x62, 0x79, 0x74, 0x65, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x66, 0x6f, 0x72, + 0x20, 0x50, 0x6f, 0x6c, 0x79, 0x31, 0x33, 0x30, 0x35, ]; const TV1_TAG: [u8; 16] = [ 0xa6, 0xf7, 0x45, 0x00, 0x8f, 0x81, 0xc9, 0x16, 0xa2, 0x0d, 0xcc, 0x74, 0xee, 0xf2, 0xb2, 0xf0, diff --git a/crypto/src/salsa.rs b/crypto/src/salsa.rs index 0711ba07c..7c1f79b30 100644 --- a/crypto/src/salsa.rs +++ b/crypto/src/salsa.rs @@ -242,14 +242,14 @@ mod tests { use crate::salsa::*; const SALSA_20_TV0_KEY: [u8; 32] = [ - 0x0f, 0x62, 0xb5, 0x08, 0x5b, 0xae, 0x01, 0x54, 0xa7, 0xfa, 0x4d, 0xa0, 0xf3, 0x46, 0x99, 0xec, 0x3f, 0x92, 0xe5, 0x38, 0x8b, 0xde, - 0x31, 0x84, 0xd7, 0x2a, 0x7d, 0xd0, 0x23, 0x76, 0xc9, 0x1c, + 0x0f, 0x62, 0xb5, 0x08, 0x5b, 0xae, 0x01, 0x54, 0xa7, 0xfa, 0x4d, 0xa0, 0xf3, 0x46, 0x99, 0xec, 0x3f, 0x92, 0xe5, 0x38, 0x8b, 0xde, 0x31, + 0x84, 0xd7, 0x2a, 0x7d, 0xd0, 0x23, 0x76, 0xc9, 0x1c, ]; const SALSA_20_TV0_IV: [u8; 8] = [0x28, 0x8f, 0xf6, 0x5d, 0xc4, 0x2b, 0x92, 0xf9]; const SALSA_20_TV0_KS: [u8; 64] = [ - 0x5e, 0x5e, 0x71, 0xf9, 0x01, 0x99, 0x34, 0x03, 0x04, 0xab, 0xb2, 0x2a, 0x37, 0xb6, 0x62, 0x5b, 0xf8, 0x83, 0xfb, 0x89, 0xce, 0x3b, - 0x21, 0xf5, 0x4a, 0x10, 0xb8, 0x10, 0x66, 0xef, 0x87, 0xda, 0x30, 0xb7, 0x76, 0x99, 0xaa, 0x73, 0x79, 0xda, 0x59, 0x5c, 0x77, 0xdd, - 0x59, 0x54, 0x2d, 0xa2, 0x08, 0xe5, 0x95, 0x4f, 0x89, 0xe4, 0x0e, 0xb7, 0xaa, 0x80, 0xa8, 0x4a, 0x61, 0x76, 0x66, 0x3f, + 0x5e, 0x5e, 0x71, 0xf9, 0x01, 0x99, 0x34, 0x03, 0x04, 0xab, 0xb2, 0x2a, 0x37, 0xb6, 0x62, 0x5b, 0xf8, 0x83, 0xfb, 0x89, 0xce, 0x3b, 0x21, + 0xf5, 0x4a, 0x10, 0xb8, 0x10, 0x66, 0xef, 0x87, 0xda, 0x30, 0xb7, 0x76, 0x99, 0xaa, 0x73, 0x79, 0xda, 0x59, 0x5c, 0x77, 0xdd, 0x59, 0x54, + 0x2d, 0xa2, 0x08, 0xe5, 0x95, 0x4f, 0x89, 0xe4, 0x0e, 0xb7, 0xaa, 0x80, 0xa8, 0x4a, 0x61, 0x76, 0x66, 0x3f, ]; #[test] diff --git a/crypto/src/secret.rs b/crypto/src/secret.rs index 666b30a7d..8705f37cf 100644 --- a/crypto/src/secret.rs +++ b/crypto/src/secret.rs @@ -37,6 +37,11 @@ impl Secret { &self.0 } + #[inline(always)] + pub fn as_bytes_mut(&mut self) -> &mut [u8; L] { + &mut self.0 + } + /// Get the first N bytes of this secret as a fixed length array. #[inline(always)] pub fn first_n(&self) -> &[u8; N] { diff --git a/network-hypervisor/src/vl1/endpoint.rs b/network-hypervisor/src/vl1/endpoint.rs index ed43805f8..21a612f65 100644 --- a/network-hypervisor/src/vl1/endpoint.rs +++ b/network-hypervisor/src/vl1/endpoint.rs @@ -216,10 +216,7 @@ impl Marshalable for Endpoint { TYPE_NIL => Ok(Endpoint::Nil), TYPE_ZEROTIER => { let zt = Address::from_bytes_fixed(buf.read_bytes_fixed(cursor)?).ok_or(UnmarshalError::InvalidData)?; - Ok(Endpoint::ZeroTier( - zt, - buf.read_bytes_fixed::(cursor)?.clone(), - )) + Ok(Endpoint::ZeroTier(zt, buf.read_bytes_fixed::(cursor)?.clone())) } TYPE_ETHERNET => Ok(Endpoint::Ethernet(MAC::unmarshal(buf, cursor)?)), TYPE_WIFIDIRECT => Ok(Endpoint::WifiDirect(MAC::unmarshal(buf, cursor)?)), @@ -230,9 +227,7 @@ impl Marshalable for Endpoint { TYPE_HTTP => Ok(Endpoint::Http( String::from_utf8_lossy(buf.read_bytes(buf.read_varint(cursor)? as usize, cursor)?).to_string(), )), - TYPE_WEBRTC => Ok(Endpoint::WebRTC( - buf.read_bytes(buf.read_varint(cursor)? as usize, cursor)?.to_vec(), - )), + TYPE_WEBRTC => Ok(Endpoint::WebRTC(buf.read_bytes(buf.read_varint(cursor)? as usize, cursor)?.to_vec())), TYPE_ZEROTIER_ENCAP => { let zt = Address::from_bytes_fixed(buf.read_bytes_fixed(cursor)?).ok_or(UnmarshalError::InvalidData)?; Ok(Endpoint::ZeroTierEncap(zt, buf.read_bytes_fixed(cursor)?.clone())) @@ -361,10 +356,7 @@ impl FromStr for Endpoint { if endpoint_type == "zt" { return Ok(Endpoint::ZeroTier(Address::from_str(address)?, hash.as_slice().try_into().unwrap())); } else { - return Ok(Endpoint::ZeroTierEncap( - Address::from_str(address)?, - hash.as_slice().try_into().unwrap(), - )); + return Ok(Endpoint::ZeroTierEncap(Address::from_str(address)?, hash.as_slice().try_into().unwrap())); } } } @@ -587,11 +579,7 @@ mod tests { let inet = crate::vl1::InetAddress::from_ip_port(&v, 1234); - for e in [ - Endpoint::Icmp(inet.clone()), - Endpoint::IpTcp(inet.clone()), - Endpoint::IpUdp(inet.clone()), - ] { + for e in [Endpoint::Icmp(inet.clone()), Endpoint::IpTcp(inet.clone()), Endpoint::IpUdp(inet.clone())] { let mut buf = Buffer::<20>::new(); let res = e.marshal(&mut buf); diff --git a/network-hypervisor/src/vl1/identity.rs b/network-hypervisor/src/vl1/identity.rs index ac3793ce5..9b1a33da2 100644 --- a/network-hypervisor/src/vl1/identity.rs +++ b/network-hypervisor/src/vl1/identity.rs @@ -222,9 +222,7 @@ impl Identity { /// It would be possible to change this in the future, with care. pub fn upgrade(&mut self) -> Result { if self.secret.is_none() { - return Err(InvalidParameterError( - "an identity can only be upgraded if it includes its private key", - )); + return Err(InvalidParameterError("an identity can only be upgraded if it includes its private key")); } if self.p384.is_none() { let p384_ecdh = P384KeyPair::generate(); @@ -292,9 +290,8 @@ impl Identity { /// This is somewhat time consuming due to the memory-intensive work algorithm. pub fn validate(self) -> Option> { if let Some(p384) = self.p384.as_ref() { - let mut self_sign_buf: Vec = Vec::with_capacity( - ADDRESS_SIZE + 4 + C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE, - ); + let mut self_sign_buf: Vec = + Vec::with_capacity(ADDRESS_SIZE + 4 + C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE); let _ = self_sign_buf.write_all(&self.address.to_bytes()); let _ = self_sign_buf.write_all(&self.x25519); let _ = self_sign_buf.write_all(&self.ed25519); @@ -331,11 +328,7 @@ impl Identity { /// /// This does NOT validate either identity. Ensure that validation has been performed. pub fn is_upgraded_from(&self, other: &Identity) -> bool { - self.address == other.address - && self.x25519 == other.x25519 - && self.ed25519 == other.ed25519 - && self.p384.is_some() - && other.p384.is_none() + self.address == other.address && self.x25519 == other.x25519 && self.ed25519 == other.ed25519 && self.p384.is_some() && other.p384.is_none() } /// Perform ECDH key agreement, returning a shared secret or None on error. @@ -493,13 +486,12 @@ impl Identity { s.push(':'); } s.push_str(":2:"); // 2 == IDENTITY_ALGORITHM_EC_NIST_P384 - let p384_joined: [u8; P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE] = - concat_arrays_4( - p384.ecdh.as_bytes(), - p384.ecdsa.as_bytes(), - &p384.ecdsa_self_signature, - &p384.ed25519_self_signature, - ); + let p384_joined: [u8; P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE] = concat_arrays_4( + p384.ecdh.as_bytes(), + p384.ecdsa.as_bytes(), + &p384.ecdsa_self_signature, + &p384.ed25519_self_signature, + ); s.push_str(base64_encode_url_nopad(&p384_joined).as_str()); if self.secret.is_some() && include_private { let secret = self.secret.as_ref().unwrap(); @@ -600,9 +592,7 @@ impl FromStr for Identity { if keys[0].len() != C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE { return Err(InvalidFormatError); } - if !keys[2].is_empty() - && keys[2].len() != P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE - { + if !keys[2].is_empty() && keys[2].len() != P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE { return Err(InvalidFormatError); } if !keys[3].is_empty() && keys[3].len() != P384_SECRET_KEY_SIZE + P384_SECRET_KEY_SIZE { @@ -632,8 +622,7 @@ impl FromStr for Identity { Some(IdentityP384Public { ecdh: ecdh.unwrap(), ecdsa: ecdsa.unwrap(), - ecdsa_self_signature: keys[2].as_slice() - [(P384_PUBLIC_KEY_SIZE * 2)..((P384_PUBLIC_KEY_SIZE * 2) + P384_ECDSA_SIGNATURE_SIZE)] + ecdsa_self_signature: keys[2].as_slice()[(P384_PUBLIC_KEY_SIZE * 2)..((P384_PUBLIC_KEY_SIZE * 2) + P384_ECDSA_SIGNATURE_SIZE)] .try_into() .unwrap(), ed25519_self_signature: keys[2].as_slice()[((P384_PUBLIC_KEY_SIZE * 2) + P384_ECDSA_SIGNATURE_SIZE)..] @@ -667,10 +656,8 @@ impl FromStr for Identity { } else { Some(IdentityP384Secret { ecdh: { - let tmp = P384KeyPair::from_bytes( - &keys[2].as_slice()[..P384_PUBLIC_KEY_SIZE], - &keys[3].as_slice()[..P384_SECRET_KEY_SIZE], - ); + let tmp = + P384KeyPair::from_bytes(&keys[2].as_slice()[..P384_PUBLIC_KEY_SIZE], &keys[3].as_slice()[..P384_SECRET_KEY_SIZE]); if tmp.is_none() { return Err(InvalidFormatError); } @@ -711,16 +698,8 @@ impl Marshalable for Identity { let x25519 = buf.read_bytes_fixed::(cursor)?; let ed25519 = buf.read_bytes_fixed::(cursor)?; - let ( - mut ecdh, - mut ecdsa, - mut ecdsa_self_signature, - mut ed25519_self_signature, - mut x25519_s, - mut ed25519_s, - mut ecdh_s, - mut ecdsa_s, - ) = (None, None, None, None, None, None, None, None); + let (mut ecdh, mut ecdsa, mut ecdsa_self_signature, mut ed25519_self_signature, mut x25519_s, mut ed25519_s, mut ecdh_s, mut ecdsa_s) = + (None, None, None, None, None, None, None, None); if type_flags == 0 { const C25519_SECRETS_SIZE: u8 = (C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE) as u8; @@ -736,9 +715,7 @@ impl Marshalable for Identity { _ => return Err(UnmarshalError::InvalidData), } } else { - if (type_flags & (Self::ALGORITHM_X25519 | Self::FLAG_INCLUDES_SECRETS)) - == (Self::ALGORITHM_X25519 | Self::FLAG_INCLUDES_SECRETS) - { + if (type_flags & (Self::ALGORITHM_X25519 | Self::FLAG_INCLUDES_SECRETS)) == (Self::ALGORITHM_X25519 | Self::FLAG_INCLUDES_SECRETS) { x25519_s = Some(buf.read_bytes_fixed::(cursor)?); ed25519_s = Some(buf.read_bytes_fixed::(cursor)?); } @@ -772,17 +749,12 @@ impl Marshalable for Identity { secret: if let Some(x25519_s) = x25519_s { Some(IdentitySecret { x25519: X25519KeyPair::from_bytes(x25519, x25519_s).ok_or(UnmarshalError::InvalidData)?, - ed25519: Ed25519KeyPair::from_bytes(ed25519, ed25519_s.ok_or(UnmarshalError::InvalidData)?) - .ok_or(UnmarshalError::InvalidData)?, + ed25519: Ed25519KeyPair::from_bytes(ed25519, ed25519_s.ok_or(UnmarshalError::InvalidData)?).ok_or(UnmarshalError::InvalidData)?, p384: if let Some(ecdh_s) = ecdh_s { Some(IdentityP384Secret { - ecdh: P384KeyPair::from_bytes(ecdh.ok_or(UnmarshalError::InvalidData)?, ecdh_s) + ecdh: P384KeyPair::from_bytes(ecdh.ok_or(UnmarshalError::InvalidData)?, ecdh_s).ok_or(UnmarshalError::InvalidData)?, + ecdsa: P384KeyPair::from_bytes(ecdsa.ok_or(UnmarshalError::InvalidData)?, ecdsa_s.ok_or(UnmarshalError::InvalidData)?) .ok_or(UnmarshalError::InvalidData)?, - ecdsa: P384KeyPair::from_bytes( - ecdsa.ok_or(UnmarshalError::InvalidData)?, - ecdsa_s.ok_or(UnmarshalError::InvalidData)?, - ) - .ok_or(UnmarshalError::InvalidData)?, }) } else { None @@ -810,9 +782,7 @@ impl Eq for Identity {} impl Ord for Identity { #[inline(always)] fn cmp(&self, other: &Self) -> Ordering { - self.address - .cmp(&other.address) - .then_with(|| self.fingerprint.cmp(&other.fingerprint)) + self.address.cmp(&other.address).then_with(|| self.fingerprint.cmp(&other.fingerprint)) } } @@ -941,11 +911,7 @@ mod tests { let gen_unmarshaled = Identity::from_bytes(bytes.as_bytes()).unwrap(); assert!(gen_unmarshaled.secret.is_some()); if !gen_unmarshaled.eq(&gen) { - println!( - "{} != {}", - hex::to_string(&gen_unmarshaled.fingerprint), - hex::to_string(&gen.fingerprint) - ); + println!("{} != {}", hex::to_string(&gen_unmarshaled.fingerprint), hex::to_string(&gen.fingerprint)); } assert!(Identity::from_str(string.as_str()).unwrap().secret.is_some()); diff --git a/network-hypervisor/src/vl1/inetaddress.rs b/network-hypervisor/src/vl1/inetaddress.rs index c95de313e..bbfed8651 100644 --- a/network-hypervisor/src/vl1/inetaddress.rs +++ b/network-hypervisor/src/vl1/inetaddress.rs @@ -892,11 +892,7 @@ impl Marshalable for InetAddress { AF_INET6 => { let b = buf.append_bytes_fixed_get_mut::<19>()?; b[0] = 6; - copy_nonoverlapping( - (&(self.sin6.sin6_addr) as *const in6_addr).cast::(), - b.as_mut_ptr().offset(1), - 16, - ); + copy_nonoverlapping((&(self.sin6.sin6_addr) as *const in6_addr).cast::(), b.as_mut_ptr().offset(1), 16); b[17] = *(&self.sin6.sin6_port as *const u16).cast::(); b[18] = *(&self.sin6.sin6_port as *const u16).cast::().offset(1); } @@ -913,10 +909,7 @@ impl Marshalable for InetAddress { Ok(InetAddress::from_ip_port(&b[0..4], u16::from_be_bytes(b[4..6].try_into().unwrap()))) } else if t == 6 { let b: &[u8; 18] = buf.read_bytes_fixed(cursor)?; - Ok(InetAddress::from_ip_port( - &b[0..16], - u16::from_be_bytes(b[16..18].try_into().unwrap()), - )) + Ok(InetAddress::from_ip_port(&b[0..16], u16::from_be_bytes(b[16..18].try_into().unwrap()))) } else { Ok(InetAddress::new()) } diff --git a/network-hypervisor/src/vl1/mac.rs b/network-hypervisor/src/vl1/mac.rs index 820d7939f..81c709929 100644 --- a/network-hypervisor/src/vl1/mac.rs +++ b/network-hypervisor/src/vl1/mac.rs @@ -27,12 +27,7 @@ impl MAC { pub fn from_bytes(b: &[u8]) -> Option { if b.len() >= 6 { NonZeroU64::new( - (b[0] as u64) << 40 - | (b[1] as u64) << 32 - | (b[2] as u64) << 24 - | (b[3] as u64) << 16 as u64 - | (b[4] as u64) << 8 - | b[5] as u64, + (b[0] as u64) << 40 | (b[1] as u64) << 32 | (b[2] as u64) << 24 | (b[3] as u64) << 16 as u64 | (b[4] as u64) << 8 | b[5] as u64, ) .map(|i| MAC(i)) } else { @@ -94,10 +89,7 @@ impl Marshalable for MAC { impl ToString for MAC { fn to_string(&self) -> String { let b: [u8; 6] = self.to_bytes(); - format!( - "{:0>2x}:{:0>2x}:{:0>2x}:{:0>2x}:{:0>2x}:{:0>2x}", - b[0], b[1], b[2], b[3], b[4], b[5] - ) + format!("{:0>2x}:{:0>2x}:{:0>2x}:{:0>2x}:{:0>2x}:{:0>2x}", b[0], b[1], b[2], b[3], b[4], b[5]) } } diff --git a/network-hypervisor/src/vl1/node.rs b/network-hypervisor/src/vl1/node.rs index 98a63bf89..4db0ec531 100644 --- a/network-hypervisor/src/vl1/node.rs +++ b/network-hypervisor/src/vl1/node.rs @@ -388,14 +388,7 @@ impl Node { /// Get the root sets that this node trusts. #[inline] pub fn root_sets(&self) -> Vec { - self.roots - .read() - .unwrap() - .sets - .values() - .cloned() - .map(|s| s.remove_typestate()) - .collect() + self.roots.read().unwrap().sets.values().cloned().map(|s| s.remove_typestate()).collect() } pub fn do_background_tasks(&self, app: &Application) -> Duration { @@ -463,8 +456,7 @@ impl Node { for (_, rs) in roots.sets.iter() { for m in rs.members.iter() { - if m.endpoints.is_some() && !address_collisions.contains(&m.identity.address) && !m.identity.eq(&self.identity) - { + if m.endpoints.is_some() && !address_collisions.contains(&m.identity.address) && !m.identity.eq(&self.identity) { debug_event!( app, "[vl1] examining root {} with {} endpoints", @@ -551,11 +543,7 @@ impl Node { ); *best_root = best.clone(); } else { - debug_event!( - app, - "[vl1] selected new best root: {} (was empty)", - best.identity.address.to_string() - ); + debug_event!(app, "[vl1] selected new best root: {} (was empty)", best.identity.address.to_string()); let _ = best_root.insert(best.clone()); } } @@ -648,11 +636,7 @@ impl Node { // Lock in write mode and remove dead paths, doing so piecemeal to again avoid blocking. for dp in dead_paths.iter() { - self.paths - .write() - .unwrap() - .get_mut::>() - .remove(dp); + self.paths.write().unwrap().get_mut::>().remove(dp); } // Finally run keepalive sends as a batch. @@ -704,15 +688,9 @@ impl Node { app, "[vl1] {} -> #{} {}->{} length {} (on socket {}@{})", source_endpoint.to_string(), - packet - .bytes_fixed_at::<8>(0) - .map_or("????????????????".into(), |pid| hex::to_string(pid)), - packet - .bytes_fixed_at::<5>(13) - .map_or("??????????".into(), |src| hex::to_string(src)), - packet - .bytes_fixed_at::<5>(8) - .map_or("??????????".into(), |dest| hex::to_string(dest)), + packet.bytes_fixed_at::<8>(0).map_or("????????????????".into(), |pid| hex::to_string(pid)), + packet.bytes_fixed_at::<5>(13).map_or("??????????".into(), |src| hex::to_string(src)), + packet.bytes_fixed_at::<5>(8).map_or("??????????".into(), |dest| hex::to_string(dest)), packet.len(), source_local_socket.to_string(), source_local_interface.to_string() @@ -780,8 +758,7 @@ impl Node { for i in 1..assembled_packet.have { if let Some(f) = assembled_packet.frags[i as usize].as_ref() { if f.len() > v1::FRAGMENT_HEADER_SIZE { - ok |= - combined_packet.append_bytes(&f.as_bytes()[v1::FRAGMENT_HEADER_SIZE..]).is_ok(); + ok |= combined_packet.append_bytes(&f.as_bytes()[v1::FRAGMENT_HEADER_SIZE..]).is_ok(); } } } @@ -831,12 +808,7 @@ impl Node { #[cfg(debug_assertions)] { debug_packet_id = u64::from_be_bytes(packet_header.id); - debug_event!( - app, - "[vl1] [v1] #{:0>16x} forwarding packet to {}", - debug_packet_id, - dest.to_string() - ); + debug_event!(app, "[vl1] [v1] #{:0>16x} forwarding packet to {}", debug_packet_id, dest.to_string()); } if packet_header.increment_hops() > v1::FORWARD_MAX_HOPS { #[cfg(debug_assertions)] @@ -993,10 +965,7 @@ impl Node { time_ticks: i64, ) -> Arc { let paths = self.paths.read().unwrap(); - if let Some(path) = paths - .get::>() - .get(&PathKey::Ref(ep, local_socket)) - { + if let Some(path) = paths.get::>().get(&PathKey::Ref(ep, local_socket)) { path.clone() } else { drop(paths); diff --git a/network-hypervisor/src/vl1/peer.rs b/network-hypervisor/src/vl1/peer.rs index 3178b213b..1b2bc3e86 100644 --- a/network-hypervisor/src/vl1/peer.rs +++ b/network-hypervisor/src/vl1/peer.rs @@ -324,11 +324,7 @@ impl Peer { let mut aes_gmac_siv = self.v1_proto_static_secret.aes_gmac_siv.get(); aes_gmac_siv.encrypt_init(&self.v1_proto_next_message_id().to_be_bytes()); - aes_gmac_siv.encrypt_set_aad(&v1::get_packet_aad_bytes( - self.identity.address, - node.identity.address, - flags_cipher_hops, - )); + aes_gmac_siv.encrypt_set_aad(&v1::get_packet_aad_bytes(self.identity.address, node.identity.address, flags_cipher_hops)); let payload = packet.as_bytes_starting_at_mut(v1::HEADER_SIZE).unwrap(); aes_gmac_siv.encrypt_first_pass(payload); aes_gmac_siv.encrypt_first_pass_finish(); @@ -419,8 +415,7 @@ impl Peer { let message_id = self.v1_proto_next_message_id(); { - let f: &mut (v1::PacketHeader, v1::message_component_structs::HelloFixedHeaderFields) = - packet.append_struct_get_mut().unwrap(); + let f: &mut (v1::PacketHeader, v1::message_component_structs::HelloFixedHeaderFields) = packet.append_struct_get_mut().unwrap(); f.0.id = message_id.to_ne_bytes(); f.0.dest = self.identity.address.to_bytes(); f.0.src = node.identity.address.to_bytes(); @@ -545,16 +540,9 @@ impl Peer { return match verb { message_type::VL1_NOP => PacketHandlerResult::Ok, message_type::VL1_HELLO => self.handle_incoming_hello(app, inner, node, time_ticks, message_id, source_path, &payload), - message_type::VL1_ERROR => self.handle_incoming_error( - app, - inner, - node, - time_ticks, - source_path, - packet_header.hops(), - message_id, - &payload, - ), + message_type::VL1_ERROR => { + self.handle_incoming_error(app, inner, node, time_ticks, source_path, packet_header.hops(), message_id, &payload) + } message_type::VL1_OK => self.handle_incoming_ok( app, inner, @@ -567,13 +555,9 @@ impl Peer { &payload, ), message_type::VL1_WHOIS => self.handle_incoming_whois(app, inner, node, time_ticks, message_id, &payload), - message_type::VL1_RENDEZVOUS => { - self.handle_incoming_rendezvous(app, node, time_ticks, message_id, source_path, &payload) - } + message_type::VL1_RENDEZVOUS => self.handle_incoming_rendezvous(app, node, time_ticks, message_id, source_path, &payload), message_type::VL1_ECHO => self.handle_incoming_echo(app, inner, node, time_ticks, message_id, &payload), - message_type::VL1_PUSH_DIRECT_PATHS => { - self.handle_incoming_push_direct_paths(app, node, time_ticks, source_path, &payload) - } + message_type::VL1_PUSH_DIRECT_PATHS => self.handle_incoming_push_direct_paths(app, node, time_ticks, source_path, &payload), message_type::VL1_USER_MESSAGE => self.handle_incoming_user_message(app, node, time_ticks, source_path, &payload), _ => inner.handle_packet(app, node, self, &source_path, packet_header.hops(), message_id, verb, &payload, 1), }; @@ -617,8 +601,7 @@ impl Peer { } self.send(app, node, Some(source_path), time_ticks, |packet| -> Result<(), Infallible> { - let f: &mut (OkHeader, v1::message_component_structs::OkHelloFixedHeaderFields) = - packet.append_struct_get_mut().unwrap(); + let f: &mut (OkHeader, v1::message_component_structs::OkHelloFixedHeaderFields) = packet.append_struct_get_mut().unwrap(); f.0.verb = message_type::VL1_OK; f.0.in_re_verb = message_type::VL1_HELLO; f.0.in_re_message_id = message_id.to_ne_bytes(); @@ -784,7 +767,7 @@ impl Peer { inner: &Inner, node: &Node, time_ticks: i64, - message_id: MessageId, + _message_id: MessageId, payload: &PacketBuffer, ) -> PacketHandlerResult { if node.this_node_is_root() || inner.should_respond_to(&self.identity) { @@ -813,12 +796,12 @@ impl Peer { fn handle_incoming_rendezvous( self: &Arc, - app: &Application, + _app: &Application, node: &Node, - time_ticks: i64, - message_id: MessageId, - source_path: &Arc, - payload: &PacketBuffer, + _time_ticks: i64, + _message_id: MessageId, + _source_path: &Arc, + _payload: &PacketBuffer, ) -> PacketHandlerResult { if node.is_peer_root(self) {} return PacketHandlerResult::Ok; @@ -853,22 +836,22 @@ impl Peer { fn handle_incoming_push_direct_paths( self: &Arc, - app: &Application, - node: &Node, - time_ticks: i64, - source_path: &Arc, - payload: &PacketBuffer, + _app: &Application, + _node: &Node, + _time_ticks: i64, + _source_path: &Arc, + _payload: &PacketBuffer, ) -> PacketHandlerResult { PacketHandlerResult::Ok } fn handle_incoming_user_message( self: &Arc, - app: &Application, - node: &Node, - time_ticks: i64, - source_path: &Arc, - payload: &PacketBuffer, + _app: &Application, + _node: &Node, + _time_ticks: i64, + _source_path: &Arc, + _payload: &PacketBuffer, ) -> PacketHandlerResult { PacketHandlerResult::Ok } diff --git a/network-hypervisor/src/vl2/networkconfig.rs b/network-hypervisor/src/vl2/networkconfig.rs index bf4f1d953..04c1d6435 100644 --- a/network-hypervisor/src/vl2/networkconfig.rs +++ b/network-hypervisor/src/vl2/networkconfig.rs @@ -116,10 +116,7 @@ impl NetworkConfig { d.set_u64(proto_v1_field_name::network_config::VERSION, 6); - d.set_str( - proto_v1_field_name::network_config::NETWORK_ID, - self.network_id.to_string().as_str(), - ); + d.set_str(proto_v1_field_name::network_config::NETWORK_ID, self.network_id.to_string().as_str()); if !self.name.is_empty() { d.set_str(proto_v1_field_name::network_config::NAME, self.name.as_str()); } @@ -253,8 +250,7 @@ impl NetworkConfig { let mut nc = Self::new(nwid, issued_to_address); - d.get_str(proto_v1_field_name::network_config::NAME) - .map(|x| nc.name = x.to_string()); + d.get_str(proto_v1_field_name::network_config::NAME).map(|x| nc.name = x.to_string()); nc.private = d.get_str(proto_v1_field_name::network_config::TYPE).map_or(true, |x| x == "1"); nc.timestamp = d .get_i64(proto_v1_field_name::network_config::TIMESTAMP) @@ -358,16 +354,10 @@ impl NetworkConfig { authentication_expiry_time: d .get_i64(proto_v1_field_name::network_config::SSO_AUTHENTICATION_EXPIRY_TIME) .unwrap_or(0), - issuer_url: d - .get_str(proto_v1_field_name::network_config::SSO_ISSUER_URL) - .unwrap_or("") - .to_string(), + issuer_url: d.get_str(proto_v1_field_name::network_config::SSO_ISSUER_URL).unwrap_or("").to_string(), nonce: d.get_str(proto_v1_field_name::network_config::SSO_NONCE).unwrap_or("").to_string(), state: d.get_str(proto_v1_field_name::network_config::SSO_STATE).unwrap_or("").to_string(), - client_id: d - .get_str(proto_v1_field_name::network_config::SSO_CLIENT_ID) - .unwrap_or("") - .to_string(), + client_id: d.get_str(proto_v1_field_name::network_config::SSO_CLIENT_ID).unwrap_or("").to_string(), }) } @@ -464,10 +454,7 @@ pub struct IpRoute { impl Marshalable for IpRoute { const MAX_MARSHAL_SIZE: usize = (InetAddress::MAX_MARSHAL_SIZE * 2) + 2 + 2; - fn marshal( - &self, - buf: &mut zerotier_utils::buffer::Buffer, - ) -> Result<(), zerotier_utils::marshalable::UnmarshalError> { + fn marshal(&self, buf: &mut zerotier_utils::buffer::Buffer) -> Result<(), zerotier_utils::marshalable::UnmarshalError> { self.target.marshal(buf)?; if let Some(via) = self.via.as_ref() { via.marshal(buf)?; diff --git a/rustfmt.toml b/rustfmt.toml index 554a221fb..c532918b9 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,5 +1,5 @@ #unstable_features = true -max_width = 140 +max_width = 150 #use_small_heuristics = "Max" edition = "2021" #empty_item_single_line = true diff --git a/service/src/main.rs b/service/src/main.rs index 52fe57242..5927891cb 100644 --- a/service/src/main.rs +++ b/service/src/main.rs @@ -171,10 +171,7 @@ fn main() { if suggested.is_empty() { eprintln!("Unrecognized option '{}'. Use 'help' for help.", invalid); } else { - eprintln!( - "Unrecognized option '{}', did you mean {}? Use 'help' for help.", - invalid, suggested - ); + eprintln!("Unrecognized option '{}', did you mean {}? Use 'help' for help.", invalid, suggested); } } std::process::exit(exitcode::ERR_USAGE); @@ -184,9 +181,7 @@ fn main() { let flags = Flags { json_output: global_args.is_present("json"), - base_path: global_args - .value_of("path") - .map_or_else(platform_default_home_path, |p| p.to_string()), + base_path: global_args.value_of("path").map_or_else(platform_default_home_path, |p| p.to_string()), auth_token_path_override: global_args.value_of("token_path").map(|p| p.to_string()), auth_token_override: global_args.value_of("token").map(|t| t.to_string()), }; diff --git a/utils/src/arrayvec.rs b/utils/src/arrayvec.rs index db76c5ebf..1018d6c8b 100644 --- a/utils/src/arrayvec.rs +++ b/utils/src/arrayvec.rs @@ -1,4 +1,10 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. +/* 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::fmt::Debug; use std::io::Write; diff --git a/utils/src/blob.rs b/utils/src/blob.rs index 0ad7b477e..b02f7902b 100644 --- a/utils/src/blob.rs +++ b/utils/src/blob.rs @@ -1,4 +1,10 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. +/* 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::fmt::Debug; diff --git a/utils/src/buffer.rs b/utils/src/buffer.rs index d7dfbf96d..cfffef323 100644 --- a/utils/src/buffer.rs +++ b/utils/src/buffer.rs @@ -1,4 +1,10 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. +/* 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::error::Error; use std::fmt::{Debug, Display}; diff --git a/utils/src/defer.rs b/utils/src/defer.rs index b66b983b4..da8e224e0 100644 --- a/utils/src/defer.rs +++ b/utils/src/defer.rs @@ -1,4 +1,11 @@ -/// Defer execution of a closure until dropped. +/* 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/ + */ + struct Defer(Option); impl Drop for Defer { @@ -8,6 +15,9 @@ impl Drop for Defer { } /// Defer execution of a closure until the return value is dropped. +/// +/// This mimics the defer statement in Go, allowing you to always do some cleanup at +/// the end of a function no matter where it exits. pub fn defer(f: F) -> impl Drop { Defer(Some(f)) } diff --git a/utils/src/dictionary.rs b/utils/src/dictionary.rs index d54c809c8..db47b3d86 100644 --- a/utils/src/dictionary.rs +++ b/utils/src/dictionary.rs @@ -1,4 +1,10 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. +/* 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::collections::BTreeMap; use std::io::Write; @@ -87,13 +93,11 @@ impl Dictionary { } pub fn get_u64(&self, k: &str) -> Option { - self.get_str(k) - .map_or(None, |s| u64::from_str_radix(s, 16).map_or(None, |i| Some(i))) + self.get_str(k).map_or(None, |s| u64::from_str_radix(s, 16).map_or(None, |i| Some(i))) } pub fn get_i64(&self, k: &str) -> Option { - self.get_str(k) - .map_or(None, |s| i64::from_str_radix(s, 16).map_or(None, |i| Some(i))) + self.get_str(k).map_or(None, |s| i64::from_str_radix(s, 16).map_or(None, |i| Some(i))) } pub fn get_bool(&self, k: &str) -> Option { diff --git a/utils/src/error.rs b/utils/src/error.rs index 1a23ea274..a958e77cd 100644 --- a/utils/src/error.rs +++ b/utils/src/error.rs @@ -1,4 +1,10 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. +/* 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::error::Error; use std::fmt::{Debug, Display}; diff --git a/utils/src/exitcode.rs b/utils/src/exitcode.rs index ad9c8e4ba..7cb96c3fa 100644 --- a/utils/src/exitcode.rs +++ b/utils/src/exitcode.rs @@ -1,4 +1,10 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. +/* 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/ + */ // These were taken from BSD sysexits.h to provide some standard for process exit codes. diff --git a/utils/src/gate.rs b/utils/src/gate.rs index f869507f0..037edcc26 100644 --- a/utils/src/gate.rs +++ b/utils/src/gate.rs @@ -1,6 +1,10 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. - -//use std::sync::atomic::{AtomicI64, Ordering}; +/* 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/ + */ /// Boolean rate limiter with normal (non-atomic) semantics. #[repr(transparent)] diff --git a/utils/src/gatherarray.rs b/utils/src/gatherarray.rs index 4442322b8..b0b0a3539 100644 --- a/utils/src/gatherarray.rs +++ b/utils/src/gatherarray.rs @@ -1,4 +1,10 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. +/* 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::mem::{size_of, MaybeUninit}; use std::ptr::copy_nonoverlapping; @@ -32,7 +38,7 @@ impl GatherArray { /// Add an item to the array if we don't have this index anymore, returning complete array if all parts are here. #[inline(always)] - pub fn add(&mut self, index: u8, value: T) -> Option> { + pub fn add_return_when_satisfied(&mut self, index: u8, value: T) -> Option> { if index < self.goal { let mut have = self.have_bits; let got = 1u64.wrapping_shl(index as u32); @@ -85,9 +91,9 @@ mod tests { for goal in 2u8..64u8 { let mut m = GatherArray::::new(goal); for x in 0..(goal - 1) { - assert!(m.add(x, x).is_none()); + assert!(m.add_return_when_satisfied(x, x).is_none()); } - let r = m.add(goal - 1, goal - 1).unwrap(); + let r = m.add_return_when_satisfied(goal - 1, goal - 1).unwrap(); for x in 0..goal { assert_eq!(r.as_ref()[x as usize], x); } diff --git a/utils/src/hex.rs b/utils/src/hex.rs index 7c3ace132..d9438e0c9 100644 --- a/utils/src/hex.rs +++ b/utils/src/hex.rs @@ -1,4 +1,10 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. +/* 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/ + */ pub const HEX_CHARS: [u8; 16] = [ b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'a', b'b', b'c', b'd', b'e', b'f', diff --git a/utils/src/io.rs b/utils/src/io.rs index 89520ccda..a67f83b4a 100644 --- a/utils/src/io.rs +++ b/utils/src/io.rs @@ -1,4 +1,10 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. +/* 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::fs::File; use std::io::Read; diff --git a/utils/src/json.rs b/utils/src/json.rs index 71c825290..4d83e6941 100644 --- a/utils/src/json.rs +++ b/utils/src/json.rs @@ -1,3 +1,11 @@ +/* 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 serde::de::DeserializeOwned; use serde::Serialize; use serde_json::ser::Formatter; @@ -36,11 +44,7 @@ pub fn json_patch(target: &mut serde_json::value::Value, source: &serde_json::va /// /// If there are no changes, None is returned. The depth limit is passed through to json_patch and /// should be set to a sanity check value to prevent overflows. -pub fn json_patch_object( - obj: O, - patch: &str, - depth_limit: usize, -) -> Result, serde_json::Error> { +pub fn json_patch_object(obj: O, patch: &str, depth_limit: usize) -> Result, serde_json::Error> { serde_json::from_str::(patch).map_or_else( |e| Err(e), |patch| { diff --git a/utils/src/lib.rs b/utils/src/lib.rs index 0ddab812b..8f211d91e 100644 --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -1,4 +1,10 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. +/* 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/ + */ pub mod arrayvec; pub mod blob; @@ -49,10 +55,7 @@ pub fn base64_decode_url_nopad(b64: &str) -> Option> { /// Get milliseconds since unix epoch. #[inline] pub fn ms_since_epoch() -> i64 { - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_millis() as i64 + std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis() as i64 } /// Get milliseconds since an arbitrary time in the past, guaranteed to monotonically increase. diff --git a/utils/src/marshalable.rs b/utils/src/marshalable.rs index 520ee1ec2..ffe6c8dcf 100644 --- a/utils/src/marshalable.rs +++ b/utils/src/marshalable.rs @@ -1,4 +1,10 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. +/* 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::error::Error; use std::fmt::{Debug, Display}; diff --git a/utils/src/memory.rs b/utils/src/memory.rs index 281c86c12..9710a9eb8 100644 --- a/utils/src/memory.rs +++ b/utils/src/memory.rs @@ -1,4 +1,10 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. +/* 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/ + */ #[allow(unused_imports)] use std::mem::{needs_drop, size_of, MaybeUninit}; diff --git a/utils/src/pool.rs b/utils/src/pool.rs index 2ed8df323..08ab98464 100644 --- a/utils/src/pool.rs +++ b/utils/src/pool.rs @@ -1,4 +1,10 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. +/* 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::ops::{Deref, DerefMut}; use std::ptr::NonNull; diff --git a/utils/src/reaper.rs b/utils/src/reaper.rs index ecfbe0b44..60624c20a 100644 --- a/utils/src/reaper.rs +++ b/utils/src/reaper.rs @@ -1,3 +1,11 @@ +/* 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::collections::VecDeque; use std::sync::Arc; diff --git a/utils/src/ringbuffer.rs b/utils/src/ringbuffer.rs index 27c3d1a2d..225fbf58d 100644 --- a/utils/src/ringbuffer.rs +++ b/utils/src/ringbuffer.rs @@ -1,4 +1,10 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. +/* 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::mem::MaybeUninit; diff --git a/utils/src/ringbuffermap.rs b/utils/src/ringbuffermap.rs index aa1b2482d..c438bb642 100644 --- a/utils/src/ringbuffermap.rs +++ b/utils/src/ringbuffermap.rs @@ -1,4 +1,10 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. +/* 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::hash::{Hash, Hasher}; use std::mem::MaybeUninit; @@ -102,8 +108,8 @@ struct Entry { /// buckets in the hash table. The maximum for both these parameters is 65535. This could be /// increased by making the index variables larger (e.g. u32 instead of u16). pub struct RingBufferMap { - salt: u32, entries: [Entry; C], + salt: u32, buckets: [u16; B], entry_ptr: u16, } diff --git a/utils/src/sync.rs b/utils/src/sync.rs index edebd1f46..baf63e339 100644 --- a/utils/src/sync.rs +++ b/utils/src/sync.rs @@ -1,3 +1,11 @@ +/* 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::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; /// Variant version of lock for RwLock with automatic conversion to a write lock as needed. diff --git a/utils/src/thing.rs b/utils/src/thing.rs index 7415b96e1..ff11d0ec5 100644 --- a/utils/src/thing.rs +++ b/utils/src/thing.rs @@ -1,4 +1,10 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. +/* 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::any::TypeId; use std::mem::{forget, size_of, MaybeUninit}; diff --git a/utils/src/varint.rs b/utils/src/varint.rs index 76e4750f0..2445d38f3 100644 --- a/utils/src/varint.rs +++ b/utils/src/varint.rs @@ -1,4 +1,10 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. +/* 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::io::{Read, Write}; diff --git a/vl1-service/src/constants.rs b/vl1-service/src/constants.rs index 436708eee..070e111ac 100644 --- a/vl1-service/src/constants.rs +++ b/vl1-service/src/constants.rs @@ -2,15 +2,15 @@ /// A list of unassigned or obsolete ports under 1024 that could possibly be squatted. pub const UNASSIGNED_PRIVILEGED_PORTS: [u16; 299] = [ - 4, 6, 8, 10, 12, 14, 15, 16, 26, 28, 30, 32, 34, 36, 40, 60, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 285, 288, 289, 290, - 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, - 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 703, 708, 713, 714, 715, 716, 717, 718, 719, 720, 721, 722, 723, 724, 725, 726, 727, - 728, 732, 733, 734, 735, 736, 737, 738, 739, 740, 743, 745, 746, 755, 756, 766, 768, 778, 779, 781, 782, 783, 784, 785, 786, 787, 788, - 789, 790, 791, 792, 793, 794, 795, 796, 797, 798, 799, 802, 803, 804, 805, 806, 807, 808, 809, 811, 812, 813, 814, 815, 816, 817, 818, - 819, 820, 821, 822, 823, 824, 825, 826, 827, 834, 835, 836, 837, 838, 839, 840, 841, 842, 843, 844, 845, 846, 849, 850, 851, 852, 853, - 854, 855, 856, 857, 858, 859, 862, 863, 864, 865, 866, 867, 868, 869, 870, 871, 872, 874, 875, 876, 877, 878, 879, 880, 881, 882, 883, - 884, 885, 889, 890, 891, 892, 893, 894, 895, 896, 897, 898, 899, 904, 905, 906, 907, 908, 909, 910, 911, 914, 915, 916, 917, 918, 919, - 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, - 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, - 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1023, + 4, 6, 8, 10, 12, 14, 15, 16, 26, 28, 30, 32, 34, 36, 40, 60, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 285, 288, 289, 290, 291, 292, + 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 334, 335, 336, 337, + 338, 339, 340, 341, 342, 343, 703, 708, 713, 714, 715, 716, 717, 718, 719, 720, 721, 722, 723, 724, 725, 726, 727, 728, 732, 733, 734, 735, 736, + 737, 738, 739, 740, 743, 745, 746, 755, 756, 766, 768, 778, 779, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, + 797, 798, 799, 802, 803, 804, 805, 806, 807, 808, 809, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 834, + 835, 836, 837, 838, 839, 840, 841, 842, 843, 844, 845, 846, 849, 850, 851, 852, 853, 854, 855, 856, 857, 858, 859, 862, 863, 864, 865, 866, 867, + 868, 869, 870, 871, 872, 874, 875, 876, 877, 878, 879, 880, 881, 882, 883, 884, 885, 889, 890, 891, 892, 893, 894, 895, 896, 897, 898, 899, 904, + 905, 906, 907, 908, 909, 910, 911, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, 931, 932, 933, 934, 935, + 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, + 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 1001, 1002, 1003, 1004, + 1005, 1006, 1007, 1008, 1009, 1023, ]; diff --git a/vl1-service/src/sys/udp.rs b/vl1-service/src/sys/udp.rs index ba8d3a949..19a950e46 100644 --- a/vl1-service/src/sys/udp.rs +++ b/vl1-service/src/sys/udp.rs @@ -31,11 +31,7 @@ fn socket_read_concurrency() -> usize { unsafe { let mut t = THREADS_PER_SOCKET; if t == 0 { - t = std::thread::available_parallelism() - .unwrap() - .get() - .max(1) - .min(MAX_PER_SOCKET_CONCURRENCY); + t = std::thread::available_parallelism().unwrap().get().max(1).min(MAX_PER_SOCKET_CONCURRENCY); THREADS_PER_SOCKET = t; } t @@ -43,13 +39,7 @@ fn socket_read_concurrency() -> usize { } pub trait UdpPacketHandler: Send + Sync { - fn incoming_udp_packet( - self: &Arc, - time_ticks: i64, - socket: &Arc, - source_address: &InetAddress, - packet: PooledPacketBuffer, - ); + fn incoming_udp_packet(self: &Arc, time_ticks: i64, socket: &Arc, source_address: &InetAddress, packet: PooledPacketBuffer); } /// A local port to which one or more UDP sockets is bound. diff --git a/vl1-service/src/vl1service.rs b/vl1-service/src/vl1service.rs index e6ed37682..f7f8c6669 100644 --- a/vl1-service/src/vl1service.rs +++ b/vl1-service/src/vl1service.rs @@ -113,8 +113,7 @@ impl VL1Service { if !state.settings.fixed_ports.contains(p) { let (total_smart_ptr_handles, most_recent_receive) = s.read().unwrap().liveness(); if total_smart_ptr_handles < most_stale_binding_liveness.0 - || (total_smart_ptr_handles == most_stale_binding_liveness.0 - && most_recent_receive <= most_stale_binding_liveness.1) + || (total_smart_ptr_handles == most_stale_binding_liveness.0 && most_recent_receive <= most_stale_binding_liveness.1) { most_stale_binding_liveness.0 = total_smart_ptr_handles; most_stale_binding_liveness.1 = most_recent_receive; diff --git a/vl1-service/src/vl1settings.rs b/vl1-service/src/vl1settings.rs index 1e928ea14..56bc4bf37 100644 --- a/vl1-service/src/vl1settings.rs +++ b/vl1-service/src/vl1settings.rs @@ -27,8 +27,7 @@ pub struct VL1Settings { impl VL1Settings { #[cfg(target_os = "macos")] - pub const DEFAULT_PREFIX_BLACKLIST: [&'static str; 11] = - ["lo", "utun", "gif", "stf", "iptap", "pktap", "feth", "zt", "llw", "anpi", "bridge"]; + pub const DEFAULT_PREFIX_BLACKLIST: [&'static str; 11] = ["lo", "utun", "gif", "stf", "iptap", "pktap", "feth", "zt", "llw", "anpi", "bridge"]; #[cfg(target_os = "linux")] pub const DEFAULT_PREFIX_BLACKLIST: [&'static str; 5] = ["lo", "tun", "tap", "ipsec", "zt"]; diff --git a/zssp/Cargo.toml b/zssp/Cargo.toml index 924691225..d4888b865 100644 --- a/zssp/Cargo.toml +++ b/zssp/Cargo.toml @@ -5,7 +5,23 @@ license = "MPL-2.0" name = "zssp" version = "0.1.0" +#[profile.release] +#opt-level = 3 +#lto = true +#codegen-units = 1 +#panic = 'abort' + +[lib] +name = "zssp" +path = "src/lib.rs" +doc = true + +[[bin]] +name = "zssp_test" +path = "src/main.rs" +doc = false + [dependencies] zerotier-utils = { path = "../utils" } zerotier-crypto = { path = "../crypto" } -pqc_kyber = { git = "https://github.com/Argyle-Software/kyber", rev = "8c7927e00f4e3508769bf69afd55b2be1c22884d", features = ["kyber1024", "std"], default-features = false } +pqc_kyber = { version = "0.4.0", default-features = false, features = ["kyber1024", "std"] } diff --git a/zssp/README.md b/zssp/README.md index 437f92059..a533cd104 100644 --- a/zssp/README.md +++ b/zssp/README.md @@ -1,8 +1,2 @@ ZeroTier Secure Socket Protocol ====== - -**NOTE: this protocol and code have not yet been formally audited and should not be used in anything production.** - -ZSSP (ZeroTier Secure Socket Protocol) is an implementation of the Noise_IK pattern using FIPS/NIST compliant primitives. After Noise_IK negotiation is complete ZSSP also adds key ratcheting and optional (enabled by default) support for quantum data forward secrecy with Kyber1024. - -It's general purpose and could be used with any system but contains a few specific design choices to make it optimal for ZeroTier and easy to distinguish from legacy ZeroTier V1 traffic for backward compatibility. diff --git a/zssp/src/applicationlayer.rs b/zssp/src/applicationlayer.rs index 6a366d590..1c89ff72f 100644 --- a/zssp/src/applicationlayer.rs +++ b/zssp/src/applicationlayer.rs @@ -1,37 +1,77 @@ -use std::ops::Deref; +/* 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 zerotier_crypto::{ - p384::{P384KeyPair, P384PublicKey}, - secret::Secret, -}; +use std::hash::Hash; -use crate::{ - sessionid::SessionId, - zssp::{ReceiveContext, Session}, -}; +use zerotier_crypto::p384::P384KeyPair; /// Trait to implement to integrate the session into an application. /// /// Templating the session on this trait lets the code here be almost entirely transport, OS, /// and use case independent. +/// +/// The constants exposed in this trait can be redefined from their defaults to change rekey +/// and negotiation timeout behavior. This is discouraged except for testing purposes when low +/// key lifetime values may be desirable to test rekeying. Also note that each side takes turns +/// initiating rekey, so if both sides don't have the same values you'll get asymmetric timing +/// behavior. This will still work as long as the key usage counter doesn't exceed the +/// EXPIRE_AFTER_USES limit. pub trait ApplicationLayer: Sized { - /// Arbitrary opaque object associated with a session, such as a connection state object. + /// Rekey after this many key uses. + /// + /// The default is 1/4 the recommended NIST limit for AES-GCM. Unless you are transferring + /// a massive amount of data REKEY_AFTER_TIME_MS is probably going to kick in first. + const REKEY_AFTER_USES: u64 = 536870912; + + /// Hard expiration after this many uses. + /// + /// 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 = 2147483647; + + /// Start attempting to rekey after a key has been in use for this many milliseconds. + /// + /// Default is two hours. + const REKEY_AFTER_TIME_MS: i64 = 1000 * 60 * 60 * 2; + + /// Maximum random jitter to add to rekey-after time. + /// + /// Default is ten minutes. + const REKEY_AFTER_TIME_MS_MAX_JITTER: u32 = 1000 * 60 * 10; + + /// Timeout for incoming Noise_XK session negotiation in milliseconds. + /// + /// Default is two seconds, which should be enough for even extremely slow links or links + /// over very long distances. + const INCOMING_SESSION_NEGOTIATION_TIMEOUT_MS: i64 = 2000; + + /// Retry interval for outgoing connection initiation or rekey attempts. + /// + /// Retry attepmpts will be no more often than this, but the delay may end up being slightly more + /// in some cases depending on where in the cycle the initial attempt falls. + const RETRY_INTERVAL: i64 = 500; + + /// Type for arbitrary opaque object for use by the application that is attached to each session. type Data; - /// Arbitrary object that dereferences to the session, such as Arc>. - type SessionRef<'a>: Deref>; - - /// A buffer containing data read from the network that can be cached. + /// Data type for incoming packet buffers. /// - /// This can be e.g. a pooled buffer that automatically returns itself to the pool when dropped. - /// It can also just be a Vec or Box<[u8]> or something like that. + /// This can be something like Vec or Box<[u8]> or it can be something like a pooled reusable + /// buffer that automatically returns to its pool when ZSSP is done with it. ZSSP may hold these + /// for a short period of time when assembling fragmented packets on the receive path. type IncomingPacketBuffer: AsRef<[u8]> + AsMut<[u8]>; - /// Remote physical address on whatever transport this session is using. - type RemoteAddress; - - /// Rate limit for attempts to rekey existing sessions in milliseconds (default: 2000). - const REKEY_RATE_LIMIT_MS: i64 = 2000; + /// Opaque type for whatever constitutes a physical path to the application. + /// + /// A physical path could be an IP address or IP plus device in the case of UDP, a socket in the + /// case of TCP, etc. + type PhysicalPath: PartialEq + Eq + Hash + Clone; /// Get a reference to this host's static public key blob. /// @@ -39,39 +79,8 @@ pub trait ApplicationLayer: Sized { /// is a byte serialized identity. It could just be a naked NIST P-384 key if that's all you need. fn get_local_s_public_blob(&self) -> &[u8]; - /// Get SHA384(this host's static public key blob). - /// - /// This allows us to avoid computing SHA384(public key blob) over and over again. - fn get_local_s_public_blob_hash(&self) -> &[u8; 48]; - - /// Get a reference to this hosts' static public key's NIST P-384 secret key pair. + /// Get a reference to this host's static public key's NIST P-384 secret key pair. /// /// This must return the NIST P-384 public key that is contained within the static public key blob. fn get_local_s_keypair(&self) -> &P384KeyPair; - - /// Extract the NIST P-384 ECC public key component from a static public key blob or return None on failure. - /// - /// This is called to parse the static public key blob from the other end and extract its NIST P-384 public - /// key. SECURITY NOTE: the information supplied here is from the wire so care must be taken to parse it - /// safely and fail on any error or corruption. - fn extract_s_public_from_raw(static_public: &[u8]) -> Option; - - /// Look up a local session by local session ID or return None if not found. - fn lookup_session<'a>(&self, local_session_id: SessionId) -> Option>; - - /// Rate limit and check an attempted new session (called before accept_new_session). - fn check_new_session(&self, rc: &ReceiveContext, remote_address: &Self::RemoteAddress) -> bool; - - /// Check whether a new session should be accepted. - /// - /// On success a tuple of local session ID, static secret, and associated object is returned. The - /// static secret is whatever results from agreement between the local and remote static public - /// keys. - fn accept_new_session( - &self, - receive_context: &ReceiveContext, - remote_address: &Self::RemoteAddress, - remote_static_public: &[u8], - remote_metadata: &[u8], - ) -> Option<(SessionId, Secret<64>, Self::Data)>; } diff --git a/zssp/src/constants.rs b/zssp/src/constants.rs deleted file mode 100644 index f38973cf1..000000000 --- a/zssp/src/constants.rs +++ /dev/null @@ -1,113 +0,0 @@ -/// Minimum size of a valid physical ZSSP packet or packet fragment. -pub const MIN_PACKET_SIZE: usize = HEADER_SIZE + AES_GCM_TAG_SIZE; - -/// Minimum physical MTU for ZSSP to function. -pub const MIN_TRANSPORT_MTU: usize = 64; - -/// Minimum recommended interval between calls to service() on each session, in milliseconds. -pub const SERVICE_INTERVAL: u64 = 10000; - -/// Setting this to true enables kyber1024 post-quantum forward secrecy. -/// -/// Kyber1024 is used for data forward secrecy but not authentication. Authentication would -/// require Kyber1024 in identities, which would make them huge, and isn't needed for our -/// threat model which is data warehousing today to decrypt tomorrow. Breaking authentication -/// is only relevant today, not in some mid to far future where a QC that can break 384-bit ECC -/// exists. -/// -/// This is normally enabled but could be disabled at build time for e.g. very small devices. -/// It might not even be necessary there to disable it since it's not that big and is usually -/// faster than NIST P-384 ECDH. -pub(crate) const JEDI: bool = true; - -/// Maximum number of fragments for data packets. -pub(crate) const MAX_FRAGMENTS: usize = 48; // hard protocol max: 63 - -/// Maximum number of fragments for key exchange packets (can be smaller to save memory, only a few needed) -pub(crate) const KEY_EXCHANGE_MAX_FRAGMENTS: usize = 2; // enough room for p384 + ZT identity + kyber1024 + tag/hmac/etc. - -/// Start attempting to rekey after a key has been used to send packets this many times. -/// This is 1/4 the recommended NIST limit for AES-GCM key lifetimes under most conditions. -pub(crate) const REKEY_AFTER_USES: u64 = 536870912; - -/// Hard expiration after this many uses. -/// -/// Use of the key beyond this point is prohibited. If we reach this number of key uses -/// the key will be destroyed in memory and the session will cease to function. A hard -/// error is also generated. -pub(crate) const EXPIRE_AFTER_USES: u64 = REKEY_AFTER_USES * 2; - -/// Start attempting to rekey after a key has been in use for this many milliseconds. -pub(crate) const REKEY_AFTER_TIME_MS: i64 = 1000 * 60 * 60; // 1 hour - -/// Maximum random jitter to add to rekey-after time. -pub(crate) const REKEY_AFTER_TIME_MS_MAX_JITTER: u32 = 1000 * 60 * 10; // 10 minutes - -/// Version 0: AES-256-GCM + NIST P-384 + optional Kyber1024 PQ forward secrecy -pub(crate) const SESSION_PROTOCOL_VERSION: u8 = 0x00; - -/// Secondary key type: none, use only P-384 for forward secrecy. -pub(crate) const HYBRID_KEY_TYPE_NONE: u8 = 0; - -/// Secondary key type: Kyber1024, PQ forward secrecy enabled. -pub(crate) const HYBRID_KEY_TYPE_KYBER1024: u8 = 1; - -/// Size of packet header -pub(crate) const HEADER_SIZE: usize = 16; - -/// Start of single block AES encryption of a portion of the header (and some data). -pub(crate) const HEADER_CHECK_ENCRYPT_START: usize = 6; - -/// End of single block AES encryption of a portion of the header (and some data). -pub(crate) const HEADER_CHECK_ENCRYPT_END: usize = 22; - -/// Size of AES-GCM keys (256 bits) -pub(crate) const AES_KEY_SIZE: usize = 32; - -/// Size of AES-GCM MAC tags -pub(crate) const AES_GCM_TAG_SIZE: usize = 16; - -/// Size of HMAC-SHA384 MAC tags -pub(crate) const HMAC_SIZE: usize = 48; - -/// Size of a session ID, which behaves a bit like a TCP port number. -/// -/// This is large since some ZeroTier nodes handle huge numbers of links, like roots and controllers. -pub(crate) const SESSION_ID_SIZE: usize = 6; - -/// Maximum difference between out-of-order incoming packet counters, and size of deduplication buffer. -pub(crate) const COUNTER_WINDOW_MAX_OUT_OF_ORDER: usize = 16; - -/// Maximum skip-ahead for counter. -/// -/// This is huge (2^24) because its real purpose is to filter out bad packets where decryption of -/// the counter yields an invalid value. -pub(crate) const COUNTER_WINDOW_MAX_SKIP_AHEAD: u64 = 16777216; - -// Packet types can range from 0 to 15 (4 bits) -- 0-3 are defined and 4-15 are reserved for future use -pub(crate) const PACKET_TYPE_DATA: u8 = 0; -pub(crate) const PACKET_TYPE_INITIAL_KEY_OFFER: u8 = 1; // "alice" -pub(crate) const PACKET_TYPE_KEY_COUNTER_OFFER: u8 = 2; // "bob" - -// Key usage labels for sub-key derivation using NIST-style KBKDF (basically just HMAC KDF). -pub(crate) const KBKDF_KEY_USAGE_LABEL_HMAC: u8 = b'M'; // HMAC-SHA384 authentication for key exchanges -pub(crate) const KBKDF_KEY_USAGE_LABEL_HEADER_CHECK: u8 = b'H'; // AES-based header check code generation -pub(crate) const KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB: u8 = b'A'; // AES-GCM in A->B direction -pub(crate) const KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE: u8 = b'B'; // AES-GCM in B->A direction -pub(crate) const KBKDF_KEY_USAGE_LABEL_RATCHETING: u8 = b'R'; // Key input for next ephemeral ratcheting - -// AES key size for header check code generation -pub(crate) const HEADER_CHECK_AES_KEY_SIZE: usize = 16; - -/// Aribitrary starting value for master key derivation. -/// -/// It doesn't matter very much what this is but it's good for it to be unique. It should -/// be changed if this code is changed in any cryptographically meaningful way like changing -/// the primary algorithm from NIST P-384 or the transport cipher from AES-GCM. -pub(crate) const INITIAL_KEY: [u8; 64] = [ - // macOS command line to generate: - // echo -n 'ZSSP_Noise_IKpsk2_NISTP384_?KYBER1024_AESGCM_SHA512' | shasum -a 512 | cut -d ' ' -f 1 | xxd -r -p | xxd -i - 0x35, 0x6a, 0x75, 0xc0, 0xbf, 0xbe, 0xc3, 0x59, 0x70, 0x94, 0x50, 0x69, 0x4c, 0xa2, 0x08, 0x40, 0xc7, 0xdf, 0x67, 0xa8, 0x68, 0x52, - 0x6e, 0xd5, 0xdd, 0x77, 0xec, 0x59, 0x6f, 0x8e, 0xa1, 0x99, 0xb4, 0x32, 0x85, 0xaf, 0x7f, 0x0d, 0xa9, 0x6c, 0x01, 0xfb, 0x72, 0x46, - 0xc0, 0x09, 0x58, 0xb8, 0xe0, 0xa8, 0xcf, 0xb1, 0x58, 0x04, 0x6e, 0x32, 0xba, 0xa8, 0xb8, 0xf9, 0x0a, 0xa4, 0xbf, 0x36, -]; diff --git a/zssp/src/error.rs b/zssp/src/error.rs index 20ab254cc..5fd35c9b4 100644 --- a/zssp/src/error.rs +++ b/zssp/src/error.rs @@ -1,8 +1,15 @@ -use crate::sessionid::SessionId; +/* 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/ + */ +#[derive(PartialEq, Eq)] pub enum Error { /// The packet was addressed to an unrecognized local session (should usually be ignored) - UnknownLocalSessionId(SessionId), + UnknownLocalSessionId, /// Packet was not well formed InvalidPacket, @@ -15,18 +22,12 @@ pub enum Error { /// There is a safe way to reply if absolutely necessary, by sending the reply back after a constant amount of time, but this is difficult to get correct. FailedAuthentication, - /// New session was rejected by the application layer. - NewSessionRejected, - /// Rekeying failed and session secret has reached its hard usage count limit MaxKeyLifetimeExceeded, /// Attempt to send using session without established key SessionNotEstablished, - /// Packet ignored by rate limiter. - RateLimited, - /// The other peer specified an unrecognized protocol version UnknownProtocolVersion, @@ -36,6 +37,9 @@ pub enum Error { /// Data object is too large to send, even with fragmentation DataTooLarge, + /// Packet counter was outside window or packet arrived with session in an unexpected state. + OutOfSequence, + /// An unexpected buffer overrun occured while attempting to encode or decode a packet. /// /// This can only ever happen if exceptionally large key blobs or metadata are being used, @@ -43,22 +47,29 @@ pub enum Error { UnexpectedBufferOverrun, } +// An I/O error in the parser means an invalid packet. +impl From for Error { + #[inline(always)] + fn from(_: std::io::Error) -> Self { + Self::InvalidPacket + } +} + impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::UnknownLocalSessionId(id) => f.write_str(format!("UnknownLocalSessionId({})", id).as_str()), - Self::InvalidPacket => f.write_str("InvalidPacket"), - Self::InvalidParameter => f.write_str("InvalidParameter"), - Self::FailedAuthentication => f.write_str("FailedAuthentication"), - Self::NewSessionRejected => f.write_str("NewSessionRejected"), - Self::MaxKeyLifetimeExceeded => f.write_str("MaxKeyLifetimeExceeded"), - Self::SessionNotEstablished => f.write_str("SessionNotEstablished"), - Self::RateLimited => f.write_str("RateLimited"), - Self::UnknownProtocolVersion => f.write_str("UnknownProtocolVersion"), - Self::DataBufferTooSmall => f.write_str("DataBufferTooSmall"), - Self::DataTooLarge => f.write_str("DataTooLarge"), - Self::UnexpectedBufferOverrun => f.write_str("UnexpectedBufferOverrun"), - } + f.write_str(match self { + Self::UnknownLocalSessionId => "UnknownLocalSessionId", + Self::InvalidPacket => "InvalidPacket", + Self::InvalidParameter => "InvalidParameter", + Self::FailedAuthentication => "FailedAuthentication", + Self::MaxKeyLifetimeExceeded => "MaxKeyLifetimeExceeded", + Self::SessionNotEstablished => "SessionNotEstablished", + Self::UnknownProtocolVersion => "UnknownProtocolVersion", + Self::DataBufferTooSmall => "DataBufferTooSmall", + Self::DataTooLarge => "DataTooLarge", + Self::OutOfSequence => "OutOfSequence", + Self::UnexpectedBufferOverrun => "UnexpectedBufferOverrun", + }) } } diff --git a/zssp/src/fragged.rs b/zssp/src/fragged.rs new file mode 100644 index 000000000..92ab6cb83 --- /dev/null +++ b/zssp/src/fragged.rs @@ -0,0 +1,107 @@ +use std::mem::{needs_drop, size_of, zeroed, MaybeUninit}; +use std::ptr::slice_from_raw_parts; + +/// Fast packet defragmenter +pub struct Fragged { + have: u64, + counter: u64, + frags: [MaybeUninit; MAX_FRAGMENTS], +} + +pub struct Assembled([MaybeUninit; MAX_FRAGMENTS], usize); + +impl AsRef<[Fragment]> for Assembled { + #[inline(always)] + fn as_ref(&self) -> &[Fragment] { + unsafe { &*slice_from_raw_parts(self.0.as_ptr().cast::(), self.1) } + } +} + +impl Drop for Assembled { + #[inline(always)] + fn drop(&mut self) { + for i in 0..self.1 { + unsafe { + self.0.get_unchecked_mut(i).assume_init_drop(); + } + } + } +} + +impl Fragged { + #[inline(always)] + pub fn new() -> Self { + debug_assert!(MAX_FRAGMENTS <= 64); + debug_assert_eq!(size_of::>(), size_of::()); + debug_assert_eq!( + size_of::<[MaybeUninit; MAX_FRAGMENTS]>(), + size_of::<[Fragment; MAX_FRAGMENTS]>() + ); + unsafe { zeroed() } + } + + /// Add a fragment and return an assembled packet container if all fragments have been received. + /// + /// When a fully assembled packet is returned the internal state is reset and this object can + /// be reused to assemble another packet. + #[inline(always)] + pub fn assemble(&mut self, counter: u64, fragment: Fragment, fragment_no: u8, fragment_count: u8) -> Option> { + if fragment_no < fragment_count && (fragment_count as usize) <= MAX_FRAGMENTS { + debug_assert!((fragment_count as usize) <= MAX_FRAGMENTS); + debug_assert!((fragment_no as usize) < MAX_FRAGMENTS); + + let mut have = self.have; + if counter != self.counter { + self.counter = counter; + if needs_drop::() { + let mut i = 0; + while have != 0 { + if (have & 1) != 0 { + debug_assert!(i < MAX_FRAGMENTS); + unsafe { self.frags.get_unchecked_mut(i).assume_init_drop() }; + } + have = have.wrapping_shr(1); + i += 1; + } + } else { + have = 0; + } + } + + unsafe { + self.frags.get_unchecked_mut(fragment_no as usize).write(fragment); + } + + let want = 0xffffffffffffffffu64.wrapping_shr((64 - fragment_count) as u32); + have |= 1u64.wrapping_shl(fragment_no as u32); + if (have & want) == want { + self.have = 0; + // Setting 'have' to 0 resets the state of this object, and the fragments + // are effectively moved into the Assembled<> container and returned. That + // container will drop them when it is dropped. + return Some(Assembled(unsafe { std::mem::transmute_copy(&self.frags) }, fragment_count as usize)); + } else { + self.have = have; + } + } + return None; + } +} + +impl Drop for Fragged { + #[inline(always)] + fn drop(&mut self) { + if needs_drop::() { + let mut have = self.have; + let mut i = 0; + while have != 0 { + if (have & 1) != 0 { + debug_assert!(i < MAX_FRAGMENTS); + unsafe { self.frags.get_unchecked_mut(i).assume_init_drop() }; + } + have = have.wrapping_shr(1); + i += 1; + } + } + } +} diff --git a/zssp/src/lib.rs b/zssp/src/lib.rs index 0c05db40f..d4b41ca62 100644 --- a/zssp/src/lib.rs +++ b/zssp/src/lib.rs @@ -1,12 +1,20 @@ +/* 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/ + */ + mod applicationlayer; mod error; +mod fragged; +mod proto; mod sessionid; -mod tests; mod zssp; -pub mod constants; - pub use crate::applicationlayer::ApplicationLayer; pub use crate::error::Error; +pub use crate::proto::{MAX_INIT_PAYLOAD_SIZE, MIN_PACKET_SIZE, MIN_TRANSPORT_MTU}; pub use crate::sessionid::SessionId; -pub use crate::zssp::{ReceiveContext, ReceiveResult, Role, Session}; +pub use crate::zssp::{Context, ReceiveResult, Session}; diff --git a/zssp/src/main.rs b/zssp/src/main.rs new file mode 100644 index 000000000..f9db5a977 --- /dev/null +++ b/zssp/src/main.rs @@ -0,0 +1,283 @@ +use std::iter::ExactSizeIterator; +use std::str::FromStr; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::mpsc; +use std::thread; +use std::time::Duration; + +use zerotier_crypto::p384::{P384KeyPair, P384PublicKey}; +use zerotier_crypto::random; +use zerotier_crypto::secret::Secret; +use zerotier_utils::hex; +use zerotier_utils::ms_monotonic; + +const TEST_MTU: usize = 1500; + +struct TestApplication { + identity_key: P384KeyPair, +} + +impl zssp::ApplicationLayer for TestApplication { + const REKEY_AFTER_USES: u64 = 300000; + 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; + type PhysicalPath = usize; + + fn get_local_s_public_blob(&self) -> &[u8] { + self.identity_key.public_key_bytes() + } + + fn get_local_s_keypair(&self) -> &zerotier_crypto::p384::P384KeyPair { + &self.identity_key + } +} + +fn alice_main( + run: &AtomicBool, + packet_success_rate: u32, + alice_app: &TestApplication, + bob_app: &TestApplication, + alice_out: mpsc::SyncSender>, + alice_in: mpsc::Receiver>, +) { + 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; TEST_MTU * 10]; + let mut up = false; + let mut last_error = zssp::Error::UnknownProtocolVersion; + + let alice_session = context + .open( + alice_app, + |b| { + let _ = alice_out.send(b.to_vec()); + }, + TEST_MTU, + bob_app.identity_key.public_key(), + Secret::default(), + None, + (), + ms_monotonic(), + ) + .unwrap(); + + println!("[alice] opening session {}", alice_session.id.to_string()); + + while run.load(Ordering::Relaxed) { + let current_time = ms_monotonic(); + loop { + let pkt = alice_in.try_recv(); + if let Ok(pkt) = pkt { + if (random::xorshift64_random() as u32) <= packet_success_rate { + 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()); + }, + &0, + &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) => { + if e != last_error { + println!("[alice] ERROR {}", e.to_string()); + last_error = e; + } + } + } + } + } else { + break; + } + } + + if up { + let ki = alice_session.key_info().unwrap(); + if ki.0 > last_ratchet_count { + last_ratchet_count = ki.0; + println!("[alice] new key! ratchet count {} fp {}", ki.0, hex::to_string(&ki.1[..16])); + } + + assert!(alice_session + .send( + |b| { + let _ = alice_out.send(b.to_vec()); + }, + &mut data_buf[..TEST_MTU], + &test_data[..1400 + ((random::xorshift64_random() as usize) % (test_data.len() - 1400))], + ) + .is_ok()); + } else { + if alice_session.established() { + up = true; + } else { + thread::sleep(Duration::from_millis(10)); + } + } + + if current_time >= next_service { + next_service = current_time + + context.service( + |_, b| { + let _ = alice_out.send(b.to_vec()); + }, + TEST_MTU, + current_time, + ); + } + } +} + +fn bob_main( + run: &AtomicBool, + packet_success_rate: u32, + _alice_app: &TestApplication, + bob_app: &TestApplication, + bob_out: mpsc::SyncSender>, + bob_in: mpsc::Receiver>, +) { + 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; + let mut last_error = zssp::Error::UnknownProtocolVersion; + + let mut bob_session = None; + + while run.load(Ordering::Relaxed) { + let pkt = bob_in.recv_timeout(Duration::from_millis(100)); + let current_time = ms_monotonic(); + + if let Ok(pkt) = pkt { + if (random::xorshift64_random() as u32) <= packet_success_rate { + match context.receive( + bob_app, + || true, + |s_public, _| Some((P384PublicKey::from_bytes(s_public).unwrap(), Secret::default(), ())), + |_, b| { + let _ = bob_out.send(b.to_vec()); + }, + &0, + &mut data_buf, + pkt, + TEST_MTU, + current_time, + ) { + Ok(zssp::ReceiveResult::Ok(_)) => { + //println!("[bob] ok"); + } + Ok(zssp::ReceiveResult::OkData(s, data)) => { + //println!("[bob] received {}", data.len()); + assert!(s + .send( + |b| { + let _ = bob_out.send(b.to_vec()); + }, + &mut data_buf_2, + data.as_mut(), + ) + .is_ok()); + transferred += data.len() as u64 * 2; // *2 because we are also sending this many bytes back + } + Ok(zssp::ReceiveResult::OkNewSession(s)) => { + println!("[bob] new session {}", s.id.to_string()); + let _ = bob_session.replace(s); + } + Ok(zssp::ReceiveResult::Rejected) => {} + Err(e) => { + if e != last_error { + println!("[bob] ERROR {}", e.to_string()); + last_error = e; + } + } + } + } + } + + if let Some(bob_session) = bob_session.as_ref() { + let ki = bob_session.key_info().unwrap(); + if ki.0 > last_ratchet_count { + last_ratchet_count = ki.0; + println!("[bob] new key! ratchet count {} fp {}", ki.0, hex::to_string(&ki.1[..16])); + } + } + + let speed_metric_elapsed = current_time - last_speed_metric; + if speed_metric_elapsed >= 1000 { + last_speed_metric = current_time; + if transferred > 0 { + println!( + "[bob] throughput: {} MiB/sec (combined input and output)", + ((transferred as f64) / 1048576.0) / ((speed_metric_elapsed as f64) / 1000.0) + ); + } + transferred = 0; + } + + if current_time >= next_service { + next_service = current_time + + context.service( + |_, b| { + let _ = bob_out.send(b.to_vec()); + }, + TEST_MTU, + current_time, + ); + } + } +} + +fn main() { + let run = AtomicBool::new(true); + + let alice_app = TestApplication { identity_key: P384KeyPair::generate() }; + let bob_app = TestApplication { identity_key: P384KeyPair::generate() }; + + let (alice_out, bob_in) = mpsc::sync_channel::>(1024); + let (bob_out, alice_in) = mpsc::sync_channel::>(1024); + + let args = std::env::args(); + let packet_success_rate = if args.len() <= 1 { + u32::MAX + } else { + ((u32::MAX as f64) * f64::from_str(args.last().unwrap().as_str()).unwrap()) as u32 + }; + + thread::scope(|ts| { + let alice_thread = ts.spawn(|| alice_main(&run, packet_success_rate, &alice_app, &bob_app, alice_out, alice_in)); + let bob_thread = ts.spawn(|| bob_main(&run, packet_success_rate, &alice_app, &bob_app, bob_out, bob_in)); + + thread::sleep(Duration::from_secs(60 * 60)); + + run.store(false, Ordering::SeqCst); + let _ = alice_thread.join(); + let _ = bob_thread.join(); + }); + + std::process::exit(0); +} diff --git a/zssp/src/proto.rs b/zssp/src/proto.rs new file mode 100644 index 000000000..ec8ce9991 --- /dev/null +++ b/zssp/src/proto.rs @@ -0,0 +1,219 @@ +/* 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::mem::size_of; + +use pqc_kyber::{KYBER_CIPHERTEXTBYTES, KYBER_PUBLICKEYBYTES}; +use zerotier_crypto::hash::{HMAC_SHA384_SIZE, SHA384_HASH_SIZE}; +use zerotier_crypto::p384::P384_PUBLIC_KEY_SIZE; + +use crate::error::Error; +use crate::sessionid::SessionId; + +/// Minimum size of a valid physical ZSSP packet of any type. Anything smaller is discarded. +pub const MIN_PACKET_SIZE: usize = HEADER_SIZE + AES_GCM_TAG_SIZE; + +/// Minimum physical MTU for ZSSP to function. +pub const MIN_TRANSPORT_MTU: usize = 128; + +/// Maximum combined size of static public blob and metadata. +pub const MAX_INIT_PAYLOAD_SIZE: usize = MAX_NOISE_HANDSHAKE_SIZE - ALICE_NOISE_XK_ACK_MIN_SIZE; + +/// Version 0: Noise_XK with NIST P-384 plus Kyber1024 hybrid exchange on session init. +pub(crate) const SESSION_PROTOCOL_VERSION: u8 = 0x00; + +/// Maximum window over which packets may be reordered. +pub(crate) const COUNTER_WINDOW_MAX_OOO: usize = 32; + +/// Maximum number of counter steps that the counter is allowed to skip ahead. +pub(crate) const COUNTER_WINDOW_MAX_SKIP_AHEAD: u64 = 16777216; + +pub(crate) const PACKET_TYPE_NOP: u8 = 0; +pub(crate) const PACKET_TYPE_DATA: u8 = 1; +pub(crate) const PACKET_TYPE_ALICE_NOISE_XK_INIT: u8 = 2; +pub(crate) const PACKET_TYPE_BOB_NOISE_XK_ACK: u8 = 3; +pub(crate) const PACKET_TYPE_ALICE_NOISE_XK_ACK: u8 = 4; +pub(crate) const PACKET_TYPE_REKEY_INIT: u8 = 5; +pub(crate) const PACKET_TYPE_REKEY_ACK: u8 = 6; + +pub(crate) const HEADER_SIZE: usize = 16; +pub(crate) const HEADER_PROTECT_ENCRYPT_START: usize = 6; +pub(crate) const HEADER_PROTECT_ENCRYPT_END: usize = 22; + +pub(crate) const KBKDF_KEY_USAGE_LABEL_INIT_ENCRYPTION: u8 = b'x'; // AES-CTR encryption during initial setup +pub(crate) const KBKDF_KEY_USAGE_LABEL_INIT_AUTHENTICATION: u8 = b'X'; // HMAC-SHA384 during initial setup +pub(crate) const KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB: u8 = b'A'; // AES-GCM in A->B direction +pub(crate) const KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE: u8 = b'B'; // AES-GCM in B->A direction +pub(crate) const KBKDF_KEY_USAGE_LABEL_RATCHET: u8 = b'R'; // Key used in derivatin of next session key + +pub(crate) const MAX_FRAGMENTS: usize = 48; // hard protocol max: 63 +pub(crate) const MAX_NOISE_HANDSHAKE_FRAGMENTS: usize = 16; // enough room for p384 + ZT identity + kyber1024 + tag/hmac/etc. +pub(crate) const MAX_NOISE_HANDSHAKE_SIZE: usize = MAX_NOISE_HANDSHAKE_FRAGMENTS * MIN_TRANSPORT_MTU; + +/// Size of keys used during derivation, mixing, etc. process. +pub(crate) const BASE_KEY_SIZE: usize = 64; + +pub(crate) const AES_256_KEY_SIZE: usize = 32; +pub(crate) const AES_HEADER_PROTECTION_KEY_SIZE: usize = 16; +pub(crate) const AES_GCM_TAG_SIZE: usize = 16; +pub(crate) const AES_GCM_NONCE_SIZE: usize = 12; + +/// The first packet in Noise_XK exchange containing Alice's ephemeral keys, session ID, and a random +/// symmetric key to protect header fragmentation fields for this session. +#[allow(unused)] +#[repr(C, packed)] +pub(crate) struct AliceNoiseXKInit { + pub header: [u8; HEADER_SIZE], + pub session_protocol_version: u8, + pub alice_noise_e: [u8; P384_PUBLIC_KEY_SIZE], + // -- start AES-CTR(es) encrypted section + pub alice_session_id: [u8; SessionId::SIZE], + pub alice_hk_public: [u8; KYBER_PUBLICKEYBYTES], + pub header_protection_key: [u8; AES_HEADER_PROTECTION_KEY_SIZE], + // -- end encrypted section + pub hmac_es: [u8; HMAC_SHA384_SIZE], +} + +impl AliceNoiseXKInit { + pub const ENC_START: usize = HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE; + pub const AUTH_START: usize = Self::ENC_START + SessionId::SIZE + KYBER_PUBLICKEYBYTES + AES_HEADER_PROTECTION_KEY_SIZE; + pub const SIZE: usize = Self::AUTH_START + HMAC_SHA384_SIZE; +} + +/// The response to AliceNoiceXKInit containing Bob's ephemeral keys. +#[allow(unused)] +#[repr(C, packed)] +pub(crate) struct BobNoiseXKAck { + pub header: [u8; HEADER_SIZE], + pub session_protocol_version: u8, + pub bob_noise_e: [u8; P384_PUBLIC_KEY_SIZE], + // -- start AES-CTR(es_ee) encrypted section + pub bob_session_id: [u8; SessionId::SIZE], + pub bob_hk_ciphertext: [u8; KYBER_CIPHERTEXTBYTES], + // -- end encrypted sectiion + pub hmac_es_ee: [u8; HMAC_SHA384_SIZE], +} + +impl BobNoiseXKAck { + pub const ENC_START: usize = HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE; + pub const AUTH_START: usize = Self::ENC_START + SessionId::SIZE + KYBER_CIPHERTEXTBYTES; + pub const SIZE: usize = Self::AUTH_START + HMAC_SHA384_SIZE; +} + +/// Alice's final response containing her identity (she already knows Bob's) and meta-data. +/* +#[allow(unused)] +#[repr(C, packed)] +pub(crate) struct AliceNoiseXKAck { + pub header: [u8; HEADER_SIZE], + pub session_protocol_version: u8, + // -- start AES-CTR(es_ee_hk) encrypted section + pub alice_static_blob_length: [u8; 2], + pub alice_static_blob: [u8; ???], + pub alice_metadata_length: [u8; 2], + pub alice_metadata: [u8; ???], + // -- end encrypted section + pub hmac_es_ee: [u8; HMAC_SHA384_SIZE], + pub hmac_es_ee_se_hk_psk: [u8; HMAC_SHA384_SIZE], +} +*/ + +pub(crate) const ALICE_NOISE_XK_ACK_ENC_START: usize = HEADER_SIZE + 1; +pub(crate) const ALICE_NOISE_XK_ACK_AUTH_SIZE: usize = HMAC_SHA384_SIZE + HMAC_SHA384_SIZE; +pub(crate) const ALICE_NOISE_XK_ACK_MIN_SIZE: usize = ALICE_NOISE_XK_ACK_ENC_START + 2 + 2 + ALICE_NOISE_XK_ACK_AUTH_SIZE; + +#[allow(unused)] +#[repr(C, packed)] +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 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 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], + // -- end AES-GCM encrypted portion + pub gcm_mac: [u8; AES_GCM_TAG_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; +} + +// Annotate only these structs as being compatible with packet_buffer_as_bytes(). These structs +// are packed flat buffers containing only byte or byte array fields, making them safe to treat +// this way even on architectures that require type size aligned access. +pub(crate) trait ProtocolFlatBuffer {} +impl ProtocolFlatBuffer for AliceNoiseXKInit {} +impl ProtocolFlatBuffer for BobNoiseXKAck {} +impl ProtocolFlatBuffer for RekeyInit {} +impl ProtocolFlatBuffer for RekeyAck {} + +#[derive(Clone, Copy)] +#[repr(C, packed)] +struct MessageNonceCreateBuffer(u64, u32); + +/// Create a 96-bit AES-GCM nonce. +/// +/// The primary information that we want to be contained here is the counter and the +/// packet type. The former makes this unique and the latter's inclusion authenticates +/// it as effectively AAD. Other elements of the header are either not authenticated, +/// like fragmentation info, or their authentication is implied via key exchange like +/// the session ID. +/// +/// This is also used as part of HMAC authentication for key exchange packets. +#[inline(always)] +pub(crate) fn create_message_nonce(packet_type: u8, counter: u64) -> [u8; AES_GCM_NONCE_SIZE] { + unsafe { std::mem::transmute(MessageNonceCreateBuffer(counter.to_le(), (packet_type as u32).to_le())) } +} + +#[inline(always)] +pub(crate) fn byte_array_as_proto_buffer(b: &[u8]) -> Result<&B, Error> { + if b.len() >= size_of::() { + Ok(unsafe { &*b.as_ptr().cast() }) + } else { + Err(Error::InvalidPacket) + } +} + +#[inline(always)] +pub(crate) fn byte_array_as_proto_buffer_mut(b: &mut [u8]) -> Result<&mut B, Error> { + if b.len() >= size_of::() { + Ok(unsafe { &mut *b.as_mut_ptr().cast() }) + } else { + Err(Error::InvalidPacket) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn check_packed_struct_sizing() { + assert_eq!(size_of::(), AliceNoiseXKInit::SIZE); + assert_eq!(size_of::(), BobNoiseXKAck::SIZE); + } +} diff --git a/zssp/src/sessionid.rs b/zssp/src/sessionid.rs index 09f32bd63..be272034c 100644 --- a/zssp/src/sessionid.rs +++ b/zssp/src/sessionid.rs @@ -1,17 +1,27 @@ +/* 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::fmt::Display; use std::num::NonZeroU64; use zerotier_crypto::random; use zerotier_utils::memory::{array_range, as_byte_array}; -use crate::constants::SESSION_ID_SIZE; - /// 48-bit session ID (most significant 16 bits of u64 are unused) #[derive(Copy, Clone, PartialEq, Eq, Hash)] #[repr(transparent)] pub struct SessionId(NonZeroU64); // stored little endian internally +const SESSION_ID_SIZE_BYTES: usize = 6; + impl SessionId { + pub const SIZE: usize = SESSION_ID_SIZE_BYTES; + pub const NONE: u64 = 0; pub const MAX: u64 = 0xffffffffffff; /// Create a new session ID, panicing if 'i' is zero or exceeds MAX. @@ -20,11 +30,18 @@ impl SessionId { Self(NonZeroU64::new(i.to_le()).unwrap()) } - /// Create a new random session ID (non-cryptographic PRNG) + /// Create a new random (non-zero) session ID (non-cryptographic PRNG) pub fn random() -> Self { Self(NonZeroU64::new(((random::xorshift64_random() % (Self::MAX - 1)) + 1).to_le()).unwrap()) } + pub(crate) fn new_from_bytes(b: &[u8; Self::SIZE]) -> Option { + let mut tmp = [0u8; 8]; + tmp[..SESSION_ID_SIZE_BYTES].copy_from_slice(b); + Self::new_from_u64_le(u64::from_ne_bytes(tmp)) + } + + /// Create from a u64 that is already in little-endian byte order. #[inline(always)] pub(crate) fn new_from_u64_le(i: u64) -> Option { NonZeroU64::new(i & Self::MAX.to_le()).map(|i| Self(i)) @@ -32,8 +49,8 @@ impl SessionId { /// Get this session ID as a little-endian byte array. #[inline(always)] - pub(crate) fn as_bytes(&self) -> &[u8; SESSION_ID_SIZE] { - array_range::(as_byte_array(&self.0)) + pub(crate) fn as_bytes(&self) -> &[u8; Self::SIZE] { + array_range::(as_byte_array(&self.0)) } } diff --git a/zssp/src/tests.rs b/zssp/src/tests.rs deleted file mode 100644 index febf1616e..000000000 --- a/zssp/src/tests.rs +++ /dev/null @@ -1,216 +0,0 @@ -#[allow(unused_imports)] -#[cfg(test)] -mod tests { - use std::collections::LinkedList; - use std::sync::{Arc, Mutex}; - use zerotier_crypto::hash::SHA384; - use zerotier_crypto::p384::{P384KeyPair, P384PublicKey}; - use zerotier_crypto::random; - use zerotier_crypto::secret::Secret; - use zerotier_utils::hex; - - use crate::*; - use constants::*; - - struct TestHost { - local_s: P384KeyPair, - local_s_hash: [u8; 48], - psk: Secret<64>, - session: Mutex>>>>, - session_id_counter: Mutex, - queue: Mutex>>, - key_id: Mutex<[u8; 16]>, - this_name: &'static str, - other_name: &'static str, - } - - impl TestHost { - fn new(psk: Secret<64>, this_name: &'static str, other_name: &'static str) -> Self { - let local_s = P384KeyPair::generate(); - let local_s_hash = SHA384::hash(local_s.public_key_bytes()); - Self { - local_s, - local_s_hash, - psk, - session: Mutex::new(None), - session_id_counter: Mutex::new(1), - queue: Mutex::new(LinkedList::new()), - key_id: Mutex::new([0; 16]), - this_name, - other_name, - } - } - } - - impl ApplicationLayer for Box { - type Data = u32; - type SessionRef<'a> = Arc>>; - type IncomingPacketBuffer = Vec; - type RemoteAddress = u32; - - const REKEY_RATE_LIMIT_MS: i64 = 0; - - fn get_local_s_public_blob(&self) -> &[u8] { - self.local_s.public_key_bytes() - } - - fn get_local_s_public_blob_hash(&self) -> &[u8; 48] { - &self.local_s_hash - } - - fn get_local_s_keypair(&self) -> &P384KeyPair { - &self.local_s - } - - fn extract_s_public_from_raw(static_public: &[u8]) -> Option { - P384PublicKey::from_bytes(static_public) - } - - fn lookup_session<'a>(&self, local_session_id: SessionId) -> Option> { - self.session.lock().unwrap().as_ref().and_then(|s| { - if s.id == local_session_id { - Some(s.clone()) - } else { - None - } - }) - } - - fn check_new_session(&self, _: &ReceiveContext, _: &Self::RemoteAddress) -> bool { - true - } - - fn accept_new_session(&self, _: &ReceiveContext, _: &u32, _: &[u8], _: &[u8]) -> Option<(SessionId, Secret<64>, Self::Data)> { - loop { - let mut new_id = self.session_id_counter.lock().unwrap(); - *new_id += 1; - return Some((SessionId::new_from_u64_le((*new_id).to_le()).unwrap(), self.psk.clone(), 0)); - } - } - } - - #[allow(unused_variables)] - #[test] - fn establish_session() { - let mut data_buf = [0_u8; (1280 - 32) * MAX_FRAGMENTS]; - let mut mtu_buffer = [0_u8; 1280]; - let mut psk: Secret<64> = Secret::default(); - random::fill_bytes_secure(&mut psk.0); - - let alice_host = Box::new(TestHost::new(psk.clone(), "alice", "bob")); - let bob_host = Box::new(TestHost::new(psk.clone(), "bob", "alice")); - let alice_rc: Box>> = Box::new(ReceiveContext::new(&alice_host)); - let bob_rc: Box>> = Box::new(ReceiveContext::new(&bob_host)); - - //println!("zssp: size of session (bytes): {}", std::mem::size_of::>>()); - - let _ = alice_host.session.lock().unwrap().insert(Arc::new( - Session::start_new( - &alice_host, - |data| bob_host.queue.lock().unwrap().push_front(data.to_vec()), - SessionId::random(), - bob_host.local_s.public_key_bytes(), - &[], - &psk, - 1, - mtu_buffer.len(), - 1, - ) - .unwrap(), - )); - - for test_loop in 0..256 { - let time_ticks = (test_loop * 10000) as i64; - for host in [&alice_host, &bob_host] { - let send_to_other = |data: &mut [u8]| { - if std::ptr::eq(host, &alice_host) { - bob_host.queue.lock().unwrap().push_front(data.to_vec()); - } else { - alice_host.queue.lock().unwrap().push_front(data.to_vec()); - } - }; - - let rc = if std::ptr::eq(host, &alice_host) { - &alice_rc - } else { - &bob_rc - }; - - loop { - if let Some(qi) = host.queue.lock().unwrap().pop_back() { - let qi_len = qi.len(); - let r = rc.receive(host, &0, send_to_other, &mut data_buf, qi, mtu_buffer.len(), time_ticks); - if r.is_ok() { - let r = r.unwrap(); - match r { - ReceiveResult::Ok => { - //println!("zssp: {} => {} ({}): Ok", host.other_name, host.this_name, qi_len); - } - ReceiveResult::OkData(data) => { - //println!("zssp: {} => {} ({}): OkData length=={}", host.other_name, host.this_name, qi_len, data.len()); - assert!(!data.iter().any(|x| *x != 0x12)); - } - ReceiveResult::OkNewSession(new_session) => { - println!("zssp: new session at {} ({})", host.this_name, u64::from(new_session.id)); - let mut hs = host.session.lock().unwrap(); - assert!(hs.is_none()); - let _ = hs.insert(Arc::new(new_session)); - } - ReceiveResult::Ignored => { - println!("zssp: {} => {} ({}): Ignored", host.other_name, host.this_name, qi_len); - } - } - } else { - println!( - "zssp: {} => {} ({}): error: {}", - host.other_name, - host.this_name, - qi_len, - r.err().unwrap().to_string() - ); - panic!(); - } - } else { - break; - } - } - - data_buf.fill(0x12); - if let Some(session) = host.session.lock().unwrap().as_ref().cloned() { - if session.established() { - { - let mut key_id = host.key_id.lock().unwrap(); - let security_info = session.status().unwrap(); - if !security_info.0.eq(key_id.as_ref()) { - *key_id = security_info.0; - println!( - "zssp: new key at {}: fingerprint {} ratchet {} kyber {} latest role {}", - host.this_name, - hex::to_string(key_id.as_ref()), - security_info.1, - security_info.3, - match security_info.2 { - Role::Alice => "A", - Role::Bob => "B", - } - ); - } - } - for _ in 0..4 { - assert!(session - .send( - send_to_other, - &mut mtu_buffer, - &data_buf[..((random::xorshift64_random() as usize) % data_buf.len())] - ) - .is_ok()); - } - if (test_loop % 13) == 0 && test_loop > 0 { - session.service(host, send_to_other, &[], mtu_buffer.len(), time_ticks, true); - } - } - } - } - } - } -} diff --git a/zssp/src/zssp.rs b/zssp/src/zssp.rs index ffef800b1..a8a8f76b0 100644 --- a/zssp/src/zssp.rs +++ b/zssp/src/zssp.rs @@ -1,59 +1,78 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. +/* 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/ + */ // ZSSP: ZeroTier Secure Session Protocol -// FIPS compliant Noise_IK with Jedi powers and built-in attack-resistant large payload (fragmentation) support. +// FIPS compliant Noise_XK with Jedi powers (Kyber1024) and built-in attack-resistant large payload (fragmentation) support. -use std::sync::atomic::{AtomicU64, Ordering}; -use std::sync::{Mutex, RwLock}; +use std::collections::{HashMap, HashSet}; +use std::num::NonZeroU64; +use std::sync::atomic::{AtomicI64, AtomicU64, Ordering}; +use std::sync::{Arc, Mutex, RwLock, Weak}; -use zerotier_crypto::aes::{Aes, AesGcm}; -use zerotier_crypto::hash::{hmac_sha512, HMACSHA384, SHA384}; -use zerotier_crypto::p384::{P384KeyPair, P384PublicKey, P384_PUBLIC_KEY_SIZE}; -use zerotier_crypto::random; +use zerotier_crypto::aes::{Aes, AesCtr, AesGcm}; +use zerotier_crypto::hash::{hmac_sha512, HMACSHA384, HMAC_SHA384_SIZE, SHA384}; +use zerotier_crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE}; use zerotier_crypto::secret::Secret; -use zerotier_crypto::secure_eq; +use zerotier_crypto::{random, secure_eq}; -use zerotier_utils::gatherarray::GatherArray; -use zerotier_utils::memory; -use zerotier_utils::ringbuffermap::RingBufferMap; -use zerotier_utils::unlikely_branch; -use zerotier_utils::varint; +use pqc_kyber::{KYBER_SECRETKEYBYTES, KYBER_SSBYTES}; use crate::applicationlayer::ApplicationLayer; -use crate::constants::*; use crate::error::Error; +use crate::fragged::Fragged; +use crate::proto::*; use crate::sessionid::SessionId; -/// Result generated by the packet receive function, with possible payloads. -pub enum ReceiveResult<'a, H: ApplicationLayer> { - /// Packet is valid, no action needs to be taken. - Ok, - - /// Packet is valid and a data payload was decoded and authenticated. - /// - /// The returned reference is to the filled parts of the data buffer supplied to receive. - OkData(&'a mut [u8]), - - /// Packet is valid and a new session was created. - /// - /// The session will have already been gated by the accept_new_session() method in ApplicationLayer. - OkNewSession(Session), - - /// Packet appears valid but was ignored e.g. as a duplicate. - Ignored, -} - -/// State information to associate with receiving contexts such as sockets or remote paths/endpoints. +/// Session context for local application. /// -/// This holds the data structures used to defragment incoming packets that are not associated with an -/// existing session, which would be new attempts to create sessions. Typically one of these is associated -/// with a single listen socket, local bound port, or other inbound endpoint. -pub struct ReceiveContext { - initial_offer_defrag: Mutex, 1024, 128>>, - incoming_init_header_check_cipher: Aes, +/// Each application using ZSSP must create an instance of this to own sessions and +/// defragment incoming packets that are not yet associated with a session. +pub struct Context { + max_incomplete_session_queue_size: usize, + defrag: Mutex< + HashMap< + (Application::PhysicalPath, u64), + Arc<( + Mutex>, + i64, // creation timestamp + )>, + >, + >, + sessions: RwLock>, } -/// A FIPS compliant variant of Noise_IK with hybrid Kyber1024 PQ data forward secrecy. +/// Lookup maps for sessions within a session context. +struct SessionsById { + // Active sessions, automatically closed if the application no longer holds their Arc<>. + active: HashMap>>, + + // Incomplete sessions in the middle of three-phase Noise_XK negotiation, expired after timeout. + incoming: HashMap>, +} + +/// Result generated by the context packet receive function, with possible payloads. +pub enum ReceiveResult<'b, Application: ApplicationLayer> { + /// Packet was valid, but no action needs to be taken and no payload was delivered. + Ok(Option>>), + + /// Packet was valid and a data payload was decoded and authenticated. + OkData(Arc>, &'b mut [u8]), + + /// Packet was valid and a new session was created. + OkNewSession(Arc>), + + /// Packet appears valid but was rejected by the application layer, e.g. a rejected new session attempt. + Rejected, +} + +/// ZeroTier Secure Session Protocol (ZSSP) Session +/// +/// A FIPS/NIST compliant variant of Noise_XK with hybrid Kyber1024 PQ data forward secrecy. pub struct Session { /// This side's locally unique session ID pub id: SessionId, @@ -61,471 +80,533 @@ pub struct Session { /// An arbitrary application defined object associated with each session pub application_data: Application::Data, - send_counter: AtomicU64, // Outgoing packet counter and nonce state - receive_window: [AtomicU64; COUNTER_WINDOW_MAX_OUT_OF_ORDER], // Receive window for anti-replay and deduplication - psk: Secret<64>, // Arbitrary PSK provided by external code - noise_ss: Secret<48>, // Static raw shared ECDH NIST P-384 key - header_check_cipher: Aes, // Cipher used for header check codes (not Noise related) - state: RwLock, // Mutable parts of state (other than defrag buffers) - remote_s_public_blob_hash: [u8; 48], // SHA384(remote static public key blob) - remote_s_public_p384_bytes: [u8; P384_PUBLIC_KEY_SIZE], // Remote NIST P-384 static public key - - defrag: Mutex, 8, 8>>, + psk: Secret, + send_counter: AtomicU64, + receive_window: [AtomicU64; COUNTER_WINDOW_MAX_OOO], + header_protection_cipher: Aes, + state: RwLock, + defrag: [Mutex>; COUNTER_WINDOW_MAX_OOO], } -struct SessionMutableState { - remote_session_id: Option, // The other side's 48-bit session ID - session_keys: [Option; 2], // Buffers to store last and latest key by 1-bit key index - cur_session_key_idx: usize, // Pointer to latest session key other side is confirmed to have - offer: Option, // Most recent ephemeral offer sent to remote - last_remote_offer: i64, // Time of most recent ephemeral offer (ms) +/// Most of the mutable parts of a session state. +struct State { + remote_session_id: Option, + keys: [Option; 2], + current_key: usize, + current_offer: Offer, +} + +struct IncomingIncompleteSession { + timestamp: i64, + alice_session_id: SessionId, + bob_session_id: SessionId, + noise_es_ee: Secret, + hk: Secret, + header_protection_key: Secret, + bob_noise_e_secret: P384KeyPair, +} + +struct OutgoingSessionInit { + last_retry_time: AtomicI64, + alice_noise_e_secret: P384KeyPair, + noise_es: Secret, + alice_hk_secret: Secret, + metadata: Option>, + init_packet: [u8; AliceNoiseXKInit::SIZE], +} + +struct OutgoingSessionAck { + last_retry_time: AtomicI64, + ack: [u8; MAX_NOISE_HANDSHAKE_SIZE], + ack_size: usize, +} + +enum Offer { + None, + NoiseXKInit(Box), + NoiseXKAck(Box), + RekeyInit(P384KeyPair, i64), } -/// A shared symmetric session key. struct SessionKey { - ratchet_count: u64, // Number of preceding session keys in ratchet - rekey_at_time: i64, // Rekey at or after this time (ticks) - rekey_at_counter: u64, // Rekey at or after this counter - expire_at_counter: u64, // Hard error when this counter value is reached or exceeded - secret_fingerprint: [u8; 16], // First 128 bits of a SHA384 computed from the secret - ratchet_key: Secret<64>, // Ratchet key for deriving the next session key - receive_key: Secret, // Receive side AES-GCM key - send_key: Secret, // Send side AES-GCM key + ratchet_key: Secret, // Key used in derivation of the next session key + receive_key: Secret, // Receive side AES-GCM key + send_key: Secret, // Send side AES-GCM key receive_cipher_pool: Mutex>>, // Pool of reusable sending ciphers send_cipher_pool: Mutex>>, // Pool of reusable receiving ciphers - role: Role, // Was this side Alice or Bob? - confirmed: bool, // We have confirmed that the other side has this key - jedi: bool, // True if Kyber1024 was used (both sides enabled) + rekey_at_time: i64, // Rekey at or after this time (ticks) + 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? + confirmed: bool, // Is this key confirmed by the other side? } -/// Alice's KEY_OFFER, remembered so Noise agreement process can resume on KEY_COUNTER_OFFER. -struct EphemeralOffer { - id: [u8; 16], // Arbitrary random offer ID - creation_time: i64, // Local time when offer was created - ratchet_count: u64, // Ratchet count (starting at zero) for initial offer - ratchet_key: Option>, // Ratchet key from previous offer or None if first offer - ss_key: Secret<64>, // Noise session key "under construction" at state after offer sent - alice_e_keypair: P384KeyPair, // NIST P-384 key pair (Noise ephemeral key for Alice) - alice_hk_keypair: Option, // Kyber1024 key pair (PQ hybrid ephemeral key for Alice) -} - -/// Was this side the one who sent the first offer (Alice) or countered (Bob). -/// -/// Note that the role can switch through the course of a session. It's the side that most recently -/// initiated a session or a rekey event. Initiator is Alice, responder is Bob. -#[derive(Clone, Copy)] -pub enum Role { - Alice, - Bob, -} - -impl Session { - /// Create a new session and send an initial key offer message to the other end. +impl Context { + /// Create a new session context. /// - /// * `app` - Interface to application using ZSSP - /// * `local_session_id` - ID for this side (Alice) of the session, must be locally unique - /// * `remote_s_public_blob` - Remote side's (Bob's) public key/identity - /// * `offer_metadata` - Arbitrary meta-data to send with key offer (empty if none) - /// * `psk` - Arbitrary pre-shared key to include as initial key material (use all zeroes if none) - /// * `application_data` - Arbitrary object to put into session - /// * `mtu` - Physical wire maximum transmission unit (current value, can change through the course of a session) - /// * `current_time` - Current monotonic time in milliseconds since an arbitrary time in the past - pub fn start_new( - app: &Application, - mut send: SendFunction, - local_session_id: SessionId, - remote_s_public_blob: &[u8], - offer_metadata: &[u8], - psk: &Secret<64>, - application_data: Application::Data, - mtu: usize, - current_time: i64, - ) -> Result { - let bob_s_public_blob = remote_s_public_blob; - if let Some(bob_s_public) = Application::extract_s_public_from_raw(bob_s_public_blob) { - if let Some(noise_ss) = app.get_local_s_keypair().agree(&bob_s_public) { - let bob_s_public_blob_hash = SHA384::hash(bob_s_public_blob); - let header_check_cipher = - Aes::new(kbkdf512(noise_ss.as_bytes(), KBKDF_KEY_USAGE_LABEL_HEADER_CHECK).first_n::()); - let mut offer = None; - if send_ephemeral_offer( - &mut send, - 1, - local_session_id, - None, - app.get_local_s_public_blob(), - offer_metadata, - &bob_s_public, - &bob_s_public_blob_hash, - &noise_ss, - None, - None, - mtu, - current_time, - &mut offer, - ) - .is_ok() - { - return Ok(Self { - id: local_session_id, - application_data, - send_counter: AtomicU64::new(2), // 1 was used above - receive_window: std::array::from_fn(|_| AtomicU64::new(0)), - psk: psk.clone(), - noise_ss, - header_check_cipher, - state: RwLock::new(SessionMutableState { - remote_session_id: None, - session_keys: [None, None], - cur_session_key_idx: 0, - offer, - last_remote_offer: i64::MIN, - }), - remote_s_public_blob_hash: bob_s_public_blob_hash, - remote_s_public_p384_bytes: bob_s_public.as_bytes().clone(), - defrag: Mutex::new(RingBufferMap::new(random::xorshift64_random() as u32)), - }); - } - } - } - return Err(Error::InvalidParameter); - } - - /// Send data over the session. - /// - /// * `send` - Function to call to send physical packet(s) - /// * `mtu_sized_buffer` - A writable work buffer whose size also specifies the physical MTU - /// * `data` - Data to send - #[inline] - pub fn send( - &self, - mut send: SendFunction, - mtu_sized_buffer: &mut [u8], - mut data: &[u8], - ) -> Result<(), Error> { - debug_assert!(mtu_sized_buffer.len() >= MIN_TRANSPORT_MTU); - let state = self.state.read().unwrap(); - if let Some(remote_session_id) = state.remote_session_id { - if let Some(session_key) = state.session_keys[state.cur_session_key_idx].as_ref() { - let counter = self.send_counter.fetch_add(1, Ordering::SeqCst); - - let mut c = session_key.get_send_cipher(counter)?; - c.reset_init_gcm(&create_message_nonce(PACKET_TYPE_DATA, counter)); - - let fragment_count = - (((data.len() + AES_GCM_TAG_SIZE) as f32) / (mtu_sized_buffer.len() - HEADER_SIZE) as f32).ceil() as usize; - let fragment_max_chunk_size = mtu_sized_buffer.len() - HEADER_SIZE; - let last_fragment_no = fragment_count - 1; - for fragment_no in 0..fragment_count { - let chunk_size = fragment_max_chunk_size.min(data.len()); - let mut fragment_size = chunk_size + HEADER_SIZE; - set_packet_header( - mtu_sized_buffer, - fragment_count, - fragment_no, - PACKET_TYPE_DATA, - u64::from(remote_session_id), - session_key.ratchet_count, - counter, - )?; - c.crypt(&data[..chunk_size], &mut mtu_sized_buffer[HEADER_SIZE..fragment_size]); - data = &data[chunk_size..]; - if fragment_no == last_fragment_no { - debug_assert!(data.is_empty()); - let tagged_fragment_size = fragment_size + AES_GCM_TAG_SIZE; - mtu_sized_buffer[fragment_size..tagged_fragment_size].copy_from_slice(&c.finish_encrypt()); - fragment_size = tagged_fragment_size; - } - self.header_check_cipher - .encrypt_block_in_place(&mut mtu_sized_buffer[HEADER_CHECK_ENCRYPT_START..HEADER_CHECK_ENCRYPT_END]); - send(&mut mtu_sized_buffer[..fragment_size]); - } - debug_assert!(data.is_empty()); - - session_key.return_send_cipher(c); - - return Ok(()); - } else { - unlikely_branch(); - } - } else { - unlikely_branch(); - } - return Err(Error::SessionNotEstablished); - } - - /// Check whether this session is established. - pub fn established(&self) -> bool { - let state = self.state.read().unwrap(); - state.remote_session_id.is_some() && state.session_keys[state.cur_session_key_idx].is_some() - } - - /// Get the shared key fingerprint, ratchet count, and whether Kyber was used, or None if not yet established. - pub fn status(&self) -> Option<([u8; 16], u64, Role, bool)> { - let state = self.state.read().unwrap(); - state.session_keys[state.cur_session_key_idx] - .as_ref() - .map(|k| (k.secret_fingerprint, k.ratchet_count, k.role, k.jedi)) - } - - /// This function needs to be called on each session at least every SERVICE_INTERVAL milliseconds. - /// - /// * `app` - Interface to application using ZSSP - /// * `send` - Function to call to send physical packet(s) - /// * `offer_metadata' - Any meta-data to include with initial key offers sent. - /// * `mtu` - Current physical transport MTU - /// * `current_time` - Current monotonic time in milliseconds - /// * `force_expire` - Re-key now regardless of key aging (if it is our turn!) - pub fn service( - &self, - app: &Application, - mut send: SendFunction, - offer_metadata: &[u8], - mtu: usize, - current_time: i64, - force_expire: bool, - ) { - let state = self.state.read().unwrap(); - if state.session_keys[state.cur_session_key_idx].as_ref().map_or(true, |k| { - matches!(k.role, Role::Bob) - && (force_expire || self.send_counter.load(Ordering::Relaxed) >= k.rekey_at_counter || current_time >= k.rekey_at_time) - }) && state - .offer - .as_ref() - .map_or(true, |o| (current_time - o.creation_time) > Application::REKEY_RATE_LIMIT_MS) - { - if let Some(remote_s_public) = P384PublicKey::from_bytes(&self.remote_s_public_p384_bytes) { - let mut offer = None; - if send_ephemeral_offer( - &mut send, - self.send_counter.fetch_add(1, Ordering::SeqCst), - self.id, - state.remote_session_id, - app.get_local_s_public_blob(), - offer_metadata, - &remote_s_public, - &self.remote_s_public_blob_hash, - &self.noise_ss, - state.session_keys[state.cur_session_key_idx].as_ref(), - if state.remote_session_id.is_some() { - Some(&self.header_check_cipher) - } else { - None - }, - mtu, - current_time, - &mut offer, - ) - .is_ok() - { - drop(state); - let _ = self.state.write().unwrap().offer.replace(offer.unwrap()); - } - } - } - } - - /// Check the receive window without mutating state. - #[inline(always)] - fn check_receive_window(&self, counter: u64) -> bool { - let c = self.receive_window[(counter as usize) % COUNTER_WINDOW_MAX_OUT_OF_ORDER].load(Ordering::Acquire); - c < counter && counter.wrapping_sub(c) < COUNTER_WINDOW_MAX_SKIP_AHEAD - } - - /// Update the receive window, returning true if the packet is still valid. - /// This should only be called after the packet is authenticated. - #[inline(always)] - fn update_receive_window(&self, counter: u64) -> bool { - let c = self.receive_window[(counter as usize) % COUNTER_WINDOW_MAX_OUT_OF_ORDER].fetch_max(counter, Ordering::AcqRel); - c < counter && counter.wrapping_sub(c) < COUNTER_WINDOW_MAX_SKIP_AHEAD - } -} - -impl ReceiveContext { - pub fn new(app: &Application) -> Self { + /// * `max_incomplete_session_queue_size` - Maximum number of incomplete sessions in negotiation phase + pub fn new(max_incomplete_session_queue_size: usize) -> Self { Self { - initial_offer_defrag: Mutex::new(RingBufferMap::new(random::next_u32_secure())), - incoming_init_header_check_cipher: Aes::new( - kbkdf512(app.get_local_s_public_blob_hash(), KBKDF_KEY_USAGE_LABEL_HEADER_CHECK).first_n::(), - ), + max_incomplete_session_queue_size, + defrag: Mutex::new(HashMap::new()), + sessions: RwLock::new(SessionsById { + active: HashMap::with_capacity(64), + incoming: HashMap::with_capacity(64), + }), } } + /// Perform periodic background service and cleanup tasks. + /// + /// This returns the number of milliseconds until it should be called again. + /// + /// * `send` - Function to send packets to remote sessions + /// * `mtu` - Physical MTU + /// * `current_time` - Current monotonic time in milliseconds + pub fn service>, &mut [u8])>(&self, mut send: SendFunction, mtu: usize, current_time: i64) -> i64 { + let mut dead_active = Vec::new(); + let mut dead_pending = Vec::new(); + let retry_cutoff = current_time - Application::RETRY_INTERVAL; + let negotiation_timeout_cutoff = current_time - Application::INCOMING_SESSION_NEGOTIATION_TIMEOUT_MS; + + // Scan sessions in read lock mode, then lock more briefly in write mode to delete any dead entries that we found. + { + let sessions = self.sessions.read().unwrap(); + for (id, s) in sessions.active.iter() { + if let Some(session) = s.upgrade() { + let state = session.state.read().unwrap(); + if match &state.current_offer { + Offer::None => true, + Offer::NoiseXKInit(offer) => { + // If there's an outstanding attempt to open a session, retransmit this periodically + // in case the initial packet doesn't make it. Note that we currently don't have + // retransmission for the intermediate steps, so a new session may still fail if the + // packet loss rate is huge. The application layer has its own logic to keep trying + // under those conditions. + if offer.last_retry_time.load(Ordering::Relaxed) < retry_cutoff { + offer.last_retry_time.store(current_time, Ordering::Relaxed); + let _ = send_with_fragmentation( + |b| send(&session, b), + &mut (offer.init_packet.clone()), + mtu, + PACKET_TYPE_ALICE_NOISE_XK_INIT, + None, + 0, + 1, + None, + ); + } + false + } + Offer::NoiseXKAck(ack) => { + // We also keep retransmitting the final ACK until we get a valid DATA or NOP packet + // from Bob, otherwise we could get a half open session. + if ack.last_retry_time.load(Ordering::Relaxed) < retry_cutoff { + ack.last_retry_time.store(current_time, Ordering::Relaxed); + let _ = send_with_fragmentation( + |b| send(&session, b), + &mut (ack.ack.clone())[..ack.ack_size], + mtu, + PACKET_TYPE_ALICE_NOISE_XK_ACK, + state.remote_session_id, + 0, + 2, + Some(&session.header_protection_cipher), + ); + } + false + } + Offer::RekeyInit(_, last_rekey_attempt_time) => *last_rekey_attempt_time < retry_cutoff, + } { + // Check whether we need to rekey if there is no pending offer or if the last rekey + // offer was before retry_cutoff (checked in the 'match' above). + if let Some(key) = state.keys[state.current_key].as_ref() { + if key.bob && (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); + } + } + } + } else { + dead_active.push(*id); + } + } + + for (id, incoming) in sessions.incoming.iter() { + if incoming.timestamp <= negotiation_timeout_cutoff { + dead_pending.push(*id); + } + } + } + + if !dead_active.is_empty() || !dead_pending.is_empty() { + let mut sessions = self.sessions.write().unwrap(); + for id in dead_active.iter() { + sessions.active.remove(id); + } + for id in dead_pending.iter() { + sessions.incoming.remove(id); + } + } + + // Delete any expired defragmentation queue items not associated with a session. + self.defrag.lock().unwrap().retain(|_, fragged| fragged.1 > negotiation_timeout_cutoff); + + Application::INCOMING_SESSION_NEGOTIATION_TIMEOUT_MS.min(Application::RETRY_INTERVAL) + } + + /// Create a new session and send initial packet(s) to other side. + /// + /// This will return Error::DataTooLarge if the combined size of the metadata and the local static public + /// blob (as retrieved from the application layer) exceed MAX_INIT_PAYLOAD_SIZE. + /// + /// * `app` - Application layer instance + /// * `send` - User-supplied packet sending function + /// * `mtu` - Physical MTU for calls to send() + /// * `remote_s_public_p384` - Remote side's static public NIST P-384 key + /// * `psk` - Pre-shared key (use all zero if none) + /// * `metadata` - Optional metadata to be included in initial handshake + /// * `application_data` - Arbitrary opaque data to include with session object + /// * `current_time` - Current monotonic time in milliseconds + pub fn open( + &self, + app: &Application, + mut send: SendFunction, + mtu: usize, + remote_s_public_p384: &P384PublicKey, + psk: Secret, + metadata: Option>, + application_data: Application::Data, + current_time: i64, + ) -> Result>, Error> { + if (metadata.as_ref().map(|md| md.len()).unwrap_or(0) + app.get_local_s_public_blob().len()) > MAX_INIT_PAYLOAD_SIZE { + return Err(Error::DataTooLarge); + } + + let alice_noise_e_secret = P384KeyPair::generate(); + let alice_noise_e = alice_noise_e_secret.public_key_bytes().clone(); + let noise_es = alice_noise_e_secret.agree(&remote_s_public_p384).ok_or(Error::InvalidParameter)?; + let alice_hk_secret = pqc_kyber::keypair(&mut random::SecureRandom::default()); + let header_protection_key: Secret = Secret(random::get_bytes_secure()); + + let (local_session_id, session) = { + let mut sessions = self.sessions.write().unwrap(); + + let mut local_session_id; + loop { + local_session_id = SessionId::random(); + if !sessions.active.contains_key(&local_session_id) && !sessions.incoming.contains_key(&local_session_id) { + break; + } + } + + let session = Arc::new(Session { + id: local_session_id, + application_data, + psk, + send_counter: AtomicU64::new(3), // 1 and 2 are reserved for init and final ack + receive_window: std::array::from_fn(|_| AtomicU64::new(0)), + header_protection_cipher: Aes::new(header_protection_key.as_bytes()), + state: RwLock::new(State { + remote_session_id: None, + keys: [None, None], + current_key: 0, + current_offer: Offer::NoiseXKInit(Box::new(OutgoingSessionInit { + last_retry_time: AtomicI64::new(current_time), + alice_noise_e_secret, + noise_es: noise_es.clone(), + alice_hk_secret: Secret(alice_hk_secret.secret), + metadata, + init_packet: [0u8; AliceNoiseXKInit::SIZE], + })), + }), + defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), + }); + + sessions.active.insert(local_session_id, Arc::downgrade(&session)); + + (local_session_id, session) + }; + + { + let mut state = session.state.write().unwrap(); + let init_packet = if let Offer::NoiseXKInit(offer) = &mut state.current_offer { + &mut offer.init_packet + } else { + panic!(); // should be impossible + }; + + let init: &mut AliceNoiseXKInit = byte_array_as_proto_buffer_mut(init_packet).unwrap(); + init.session_protocol_version = SESSION_PROTOCOL_VERSION; + init.alice_noise_e = alice_noise_e; + init.alice_session_id = *local_session_id.as_bytes(); + init.alice_hk_public = alice_hk_secret.public; + init.header_protection_key = header_protection_key.0; + + aes_ctr_crypt_one_time_use_key( + kbkdf::(noise_es.as_bytes()).as_bytes(), + &mut init_packet[AliceNoiseXKInit::ENC_START..AliceNoiseXKInit::AUTH_START], + ); + + let hmac = hmac_sha384_2( + kbkdf::(noise_es.as_bytes()).as_bytes(), + &create_message_nonce(PACKET_TYPE_ALICE_NOISE_XK_INIT, 1), + &init_packet[HEADER_SIZE..AliceNoiseXKInit::AUTH_START], + ); + init_packet[AliceNoiseXKInit::AUTH_START..AliceNoiseXKInit::AUTH_START + HMAC_SHA384_SIZE].copy_from_slice(&hmac); + + send_with_fragmentation( + &mut send, + &mut (init_packet.clone()), + mtu, + PACKET_TYPE_ALICE_NOISE_XK_INIT, + None, + 0, + 1, + None, + )?; + } + + return Ok(session); + } + /// Receive, authenticate, decrypt, and process a physical wire packet. /// + /// The send function may be called one or more times to send packets. If the packet is associated + /// wtth an active session this session is supplied, otherwise this parameter is None and the packet + /// should be a reply to the current incoming packet. The size of packets to be sent will not exceed + /// the supplied mtu. + /// + /// The check_allow_incoming_session function is called when an initial Noise_XK init message is + /// received. This is before anything is known about the caller. A return value of true proceeds + /// with negotiation. False drops the packet. + /// + /// The check_accept_session function is called at the end of negotiation for an incoming session + /// with the caller's static public blob and meta-data if any. It must return the P-384 static public + /// key extracted from the supplied blob, a PSK (or all zeroes if none), and application data to + /// associate with the new session. A return of None abandons the session. + /// + /// Note that if check_accept_session accepts and returns Some() the session could still fail with + /// receive() returning an error. A Some() return from check_accept_sesion doesn't guarantee + /// successful new session init, only that the application has authorized it. + /// + /// Finally, note that the check_X() functions can end up getting called more than once for a given + /// incoming attempt from a given node if the network quality is poor. That's because the caller may + /// have to retransmit init packets causing repetition of parts of the exchange. + /// /// * `app` - Interface to application using ZSSP - /// * `remote_address` - Remote physical address of source endpoint + /// * `check_allow_incoming_session` - Function to call to check whether an unidentified new session should be accepted + /// * `check_accept_session` - Function to accept sessions after final negotiation, or returns None if rejected + /// * `send` - Function to call to send packets /// * `data_buf` - Buffer to receive decrypted and authenticated object data (an error is returned if too small) /// * `incoming_packet_buf` - Buffer containing incoming wire packet (receive() takes ownership) /// * `mtu` - Physical wire MTU for sending packets /// * `current_time` - Current monotonic time in milliseconds - #[inline] - pub fn receive<'a, SendFunction: FnMut(&mut [u8])>( + pub fn receive< + 'b, + SendFunction: FnMut(Option<&Arc>>, &mut [u8]), + CheckAllowIncomingSession: FnMut() -> bool, + CheckAcceptSession: FnMut(&[u8], Option<&[u8]>) -> Option<(P384PublicKey, Secret<64>, Application::Data)>, + >( &self, app: &Application, - remote_address: &Application::RemoteAddress, + mut check_allow_incoming_session: CheckAllowIncomingSession, + mut check_accept_session: CheckAcceptSession, mut send: SendFunction, - data_buf: &'a mut [u8], + source: &Application::PhysicalPath, + data_buf: &'b mut [u8], mut incoming_packet_buf: Application::IncomingPacketBuffer, mtu: usize, current_time: i64, - ) -> Result, Error> { + ) -> Result, Error> { let incoming_packet: &mut [u8] = incoming_packet_buf.as_mut(); if incoming_packet.len() < MIN_PACKET_SIZE { - unlikely_branch(); return Err(Error::InvalidPacket); } - if let Some(local_session_id) = SessionId::new_from_u64_le(memory::load_raw(incoming_packet)) { - if let Some(session) = app.lookup_session(local_session_id) { - session - .header_check_cipher - .decrypt_block_in_place(&mut incoming_packet[HEADER_CHECK_ENCRYPT_START..HEADER_CHECK_ENCRYPT_END]); - let raw_header_a = u16::from_le(memory::load_raw(&incoming_packet[6..])); - let key_index = (raw_header_a & 1) as usize; - let packet_type = (raw_header_a.wrapping_shr(1) & 7) as u8; - let fragment_count = ((raw_header_a.wrapping_shr(4) & 63) + 1) as u8; - let fragment_no = raw_header_a.wrapping_shr(10) as u8; - let counter = u64::from_le(memory::load_raw(&incoming_packet[8..])); + let mut incoming = None; + if let Some(local_session_id) = SessionId::new_from_u64_le(u64::from_le_bytes(incoming_packet[0..8].try_into().unwrap())) { + if let Some(session) = self.sessions.read().unwrap().active.get(&local_session_id).and_then(|s| s.upgrade()) { + debug_assert!(!self.sessions.read().unwrap().incoming.contains_key(&local_session_id)); - if session.check_receive_window(counter) { + session + .header_protection_cipher + .decrypt_block_in_place(&mut incoming_packet[HEADER_PROTECT_ENCRYPT_START..HEADER_PROTECT_ENCRYPT_END]); + let (key_index, packet_type, fragment_count, fragment_no, incoming_counter) = parse_packet_header(&incoming_packet); + + if session.check_receive_window(incoming_counter) { if fragment_count > 1 { - if fragment_count <= (MAX_FRAGMENTS as u8) && fragment_no < fragment_count { - let mut defrag = session.defrag.lock().unwrap(); - let fragment_gather_array = defrag.get_or_create_mut(&counter, || GatherArray::new(fragment_count)); - if let Some(assembled_packet) = fragment_gather_array.add(fragment_no, incoming_packet_buf) { - drop(defrag); // release lock - return self.receive_complete( - app, - remote_address, - &mut send, - data_buf, - counter, - assembled_packet.as_ref(), - packet_type, - Some(session), - key_index, - mtu, - current_time, - ); - } + let mut fragged = session.defrag[(incoming_counter as usize) % COUNTER_WINDOW_MAX_OOO].lock().unwrap(); + if let Some(assembled_packet) = fragged.assemble(incoming_counter, incoming_packet_buf, fragment_no, fragment_count) { + drop(fragged); + return self.process_complete_incoming_packet( + app, + &mut send, + &mut check_allow_incoming_session, + &mut check_accept_session, + data_buf, + incoming_counter, + assembled_packet.as_ref(), + packet_type, + Some(session), + None, + key_index, + mtu, + current_time, + ); } else { - unlikely_branch(); - return Err(Error::InvalidPacket); + drop(fragged); + return Ok(ReceiveResult::Ok(Some(session))); } } else { - return self.receive_complete( + return self.process_complete_incoming_packet( app, - remote_address, &mut send, + &mut check_allow_incoming_session, + &mut check_accept_session, data_buf, - counter, + incoming_counter, &[incoming_packet_buf], packet_type, Some(session), + None, key_index, mtu, current_time, ); } } else { - unlikely_branch(); - return Ok(ReceiveResult::Ignored); + return Err(Error::OutOfSequence); } } else { - unlikely_branch(); - return Err(Error::UnknownLocalSessionId(local_session_id)); + if let Some(i) = self.sessions.read().unwrap().incoming.get(&local_session_id).cloned() { + Aes::new(i.header_protection_key.as_bytes()) + .decrypt_block_in_place(&mut incoming_packet[HEADER_PROTECT_ENCRYPT_START..HEADER_PROTECT_ENCRYPT_END]); + incoming = Some(i); + } else { + return Err(Error::UnknownLocalSessionId); + } } - } else { - unlikely_branch(); // we want data receive to be the priority branch, this is only occasionally used + } - self.incoming_init_header_check_cipher - .decrypt_block_in_place(&mut incoming_packet[HEADER_CHECK_ENCRYPT_START..HEADER_CHECK_ENCRYPT_END]); - let raw_header_a = u16::from_le(memory::load_raw(&incoming_packet[6..])); - let key_index = (raw_header_a & 1) as usize; - let packet_type = (raw_header_a.wrapping_shr(1) & 7) as u8; - let fragment_count = ((raw_header_a.wrapping_shr(4) & 63) + 1) as u8; - let fragment_no = raw_header_a.wrapping_shr(10) as u8; - let counter = u64::from_le(memory::load_raw(&incoming_packet[8..])); + // If we make it here the packet is not associated with a session or is associated with an + // incoming session (Noise_XK mid-negotiation). - if fragment_count > 1 { - let mut defrag = self.initial_offer_defrag.lock().unwrap(); - let fragment_gather_array = defrag.get_or_create_mut(&counter, || GatherArray::new(fragment_count)); - if let Some(assembled_packet) = fragment_gather_array.add(fragment_no, incoming_packet_buf) { - drop(defrag); // release lock - return self.receive_complete( - app, - remote_address, - &mut send, - data_buf, - counter, - assembled_packet.as_ref(), - packet_type, - None, - key_index, - mtu, - current_time, - ); + let (key_index, packet_type, fragment_count, fragment_no, incoming_counter) = parse_packet_header(&incoming_packet); + if fragment_count > 1 { + let f = { + let mut defrag = self.defrag.lock().unwrap(); + let f = defrag + .entry((source.clone(), incoming_counter)) + .or_insert_with(|| Arc::new((Mutex::new(Fragged::new()), current_time))) + .clone(); + + // Anti-DOS overflow purge of the incoming defragmentation queue for packets not associated with known sessions. + if defrag.len() >= self.max_incomplete_session_queue_size { + // First, drop all entries that are timed out or whose physical source duplicates another entry. + let mut sources = HashSet::with_capacity(defrag.len()); + let negotiation_timeout_cutoff = current_time - Application::INCOMING_SESSION_NEGOTIATION_TIMEOUT_MS; + defrag.retain(|k, fragged| (fragged.1 > negotiation_timeout_cutoff && sources.insert(k.0.clone())) || Arc::ptr_eq(fragged, &f)); + + // Then, if we are still at or over the limit, drop 10% of remaining entries at random. + if defrag.len() >= self.max_incomplete_session_queue_size { + let mut rn = random::next_u32_secure(); + defrag.retain(|_, fragged| { + rn = prng32(rn); + rn > (u32::MAX / 10) || Arc::ptr_eq(fragged, &f) + }); + } } - } else { - return self.receive_complete( + + f + }; + let mut fragged = f.0.lock().unwrap(); + + if let Some(assembled_packet) = fragged.assemble(incoming_counter, incoming_packet_buf, fragment_no, fragment_count) { + self.defrag.lock().unwrap().remove(&(source.clone(), incoming_counter)); + return self.process_complete_incoming_packet( app, - remote_address, &mut send, + &mut check_allow_incoming_session, + &mut check_accept_session, data_buf, - counter, - &[incoming_packet_buf], + incoming_counter, + assembled_packet.as_ref(), packet_type, None, + incoming, key_index, mtu, current_time, ); } - }; + } else { + return self.process_complete_incoming_packet( + app, + &mut send, + &mut check_allow_incoming_session, + &mut check_accept_session, + data_buf, + incoming_counter, + &[incoming_packet_buf], + packet_type, + None, + incoming, + key_index, + mtu, + current_time, + ); + } - return Ok(ReceiveResult::Ok); + return Ok(ReceiveResult::Ok(None)); } - /// Called internally when all fragments of a packet are received. - /// - /// NOTE: header check codes will already have been validated on receipt of each fragment. AEAD authentication - /// and decryption has NOT yet been performed, and is done here. - fn receive_complete<'a, SendFunction: FnMut(&mut [u8])>( + fn process_complete_incoming_packet< + 'b, + SendFunction: FnMut(Option<&Arc>>, &mut [u8]), + CheckAllowIncomingSession: FnMut() -> bool, + CheckAcceptSession: FnMut(&[u8], Option<&[u8]>) -> Option<(P384PublicKey, Secret<64>, Application::Data)>, + >( &self, app: &Application, - remote_address: &Application::RemoteAddress, send: &mut SendFunction, - data_buf: &'a mut [u8], - counter: u64, + check_allow_incoming_session: &mut CheckAllowIncomingSession, + check_accept_session: &mut CheckAcceptSession, + data_buf: &'b mut [u8], + incoming_counter: u64, fragments: &[Application::IncomingPacketBuffer], packet_type: u8, - session: Option>, + session: Option>>, + incoming: Option>, key_index: usize, mtu: usize, current_time: i64, - ) -> Result, Error> { + ) -> Result, Error> { debug_assert!(fragments.len() >= 1); - let message_nonce = create_message_nonce(packet_type, counter); - if packet_type == PACKET_TYPE_DATA { + // Generate incoming message nonce for decryption and authentication. + let incoming_message_nonce = create_message_nonce(packet_type, incoming_counter); + + if packet_type <= PACKET_TYPE_DATA { if let Some(session) = session { let state = session.state.read().unwrap(); - if let Some(session_key) = state.session_keys[key_index].as_ref() { - let mut c = session_key.get_receive_cipher(); - c.reset_init_gcm(&message_nonce); + if let Some(key) = state.keys[key_index].as_ref() { + let mut c = key.get_receive_cipher(); + c.reset_init_gcm(&incoming_message_nonce); let mut data_len = 0; // Decrypt fragments 0..N-1 where N is the number of fragments. for f in fragments[..(fragments.len() - 1)].iter() { - let f = f.as_ref(); + let f: &[u8] = f.as_ref(); debug_assert!(f.len() >= HEADER_SIZE); let current_frag_data_start = data_len; data_len += f.len() - HEADER_SIZE; if data_len > data_buf.len() { - unlikely_branch(); - session_key.return_receive_cipher(c); + key.return_receive_cipher(c); return Err(Error::DataBufferTooSmall); } c.crypt(&f[HEADER_SIZE..], &mut data_buf[current_frag_data_start..data_len]); @@ -535,771 +616,921 @@ impl ReceiveContext { let current_frag_data_start = data_len; let last_fragment = fragments.last().unwrap().as_ref(); if last_fragment.len() < (HEADER_SIZE + AES_GCM_TAG_SIZE) { - unlikely_branch(); return Err(Error::InvalidPacket); } data_len += last_fragment.len() - (HEADER_SIZE + AES_GCM_TAG_SIZE); if data_len > data_buf.len() { - unlikely_branch(); - session_key.return_receive_cipher(c); + key.return_receive_cipher(c); return Err(Error::DataBufferTooSmall); } let payload_end = last_fragment.len() - AES_GCM_TAG_SIZE; - c.crypt( - &last_fragment[HEADER_SIZE..payload_end], - &mut data_buf[current_frag_data_start..data_len], - ); + c.crypt(&last_fragment[HEADER_SIZE..payload_end], &mut data_buf[current_frag_data_start..data_len]); - let gcm_tag = &last_fragment[payload_end..]; - let aead_authentication_ok = c.finish_decrypt(gcm_tag); - session_key.return_receive_cipher(c); + let aead_authentication_ok = c.finish_decrypt(&last_fragment[payload_end..]); + key.return_receive_cipher(c); if aead_authentication_ok { - if session.update_receive_window(counter) { - // If the packet authenticated, this confirms that the other side indeed - // knows this session key. In that case mark the session key as confirmed - // and if the current active key is older switch it to point to this one. - if !session_key.confirmed { - unlikely_branch(); - let this_ratchet_count = session_key.ratchet_count; + if session.update_receive_window(incoming_counter) { + // Update the current key to point to this key if it's newer, since having received + // a packet encrypted with it proves that the other side has successfully derived it + // as well. + if state.current_key == key_index && key.confirmed { + drop(state); + } else { + let current_key_created_at_counter = key.created_at_counter; + drop(state); let mut state = session.state.write().unwrap(); - state.session_keys[key_index].as_mut().unwrap().confirmed = true; - if state.cur_session_key_idx != key_index { - if let Some(other_session_key) = state.session_keys[state.cur_session_key_idx].as_ref() { - if other_session_key.ratchet_count < this_ratchet_count { - state.cur_session_key_idx = key_index; + if state.current_key != key_index { + if let Some(other_session_key) = state.keys[state.current_key].as_ref() { + if other_session_key.created_at_counter < current_key_created_at_counter { + state.current_key = key_index; } } else { - state.cur_session_key_idx = key_index; + state.current_key = key_index; } } + state.keys[key_index].as_mut().unwrap().confirmed = true; + + // If we got a valid data packet from Bob, this means we can cancel any offers + // that are still oustanding for initialization. + match &state.current_offer { + Offer::NoiseXKInit(_) | Offer::NoiseXKAck(_) => { + state.current_offer = Offer::None; + } + _ => {} + } } - return Ok(ReceiveResult::OkData(&mut data_buf[..data_len])); + if packet_type == PACKET_TYPE_DATA { + return Ok(ReceiveResult::OkData(session, &mut data_buf[..data_len])); + } else { + return Ok(ReceiveResult::Ok(Some(session))); + } } else { - unlikely_branch(); - return Ok(ReceiveResult::Ignored); + return Err(Error::OutOfSequence); } } } + return Err(Error::FailedAuthentication); } else { - unlikely_branch(); - return Err(Error::SessionNotEstablished); + return Err(Error::UnknownLocalSessionId); } } else { - unlikely_branch(); - - // To greatly simplify logic handling key exchange packets, assemble these first. - // Handling KEX packets isn't the fast path so the extra copying isn't significant. - const KEX_BUF_LEN: usize = 4096; - let mut kex_packet = [0_u8; KEX_BUF_LEN]; - let mut kex_packet_len = 0; - for i in 0..fragments.len() { - let mut ff = fragments[i].as_ref(); - if ff.len() < MIN_PACKET_SIZE { - return Err(Error::InvalidPacket); - } - if i > 0 { - ff = &ff[HEADER_SIZE..]; - } - let j = kex_packet_len + ff.len(); - if j > KEX_BUF_LEN { - return Err(Error::InvalidPacket); - } - kex_packet[kex_packet_len..j].copy_from_slice(ff); - kex_packet_len = j; + // For Noise setup/KEX packets go ahead and pre-assemble all fragments to simplify the code below. + let mut pkt_assembly_buffer = [0u8; MAX_NOISE_HANDSHAKE_SIZE]; + let pkt_assembled_size = assemble_fragments_into::(fragments, &mut pkt_assembly_buffer)?; + if pkt_assembled_size < MIN_PACKET_SIZE { + return Err(Error::InvalidPacket); } - let kex_packet_saved_ciphertext = kex_packet.clone(); // save for HMAC check later - - // Key exchange packets begin (after header) with the session protocol version. This could be - // changed in the future to support a different cipher suite. - if kex_packet[HEADER_SIZE] != SESSION_PROTOCOL_VERSION { + let pkt_assembled = &mut pkt_assembly_buffer[..pkt_assembled_size]; + if pkt_assembled[HEADER_SIZE] != SESSION_PROTOCOL_VERSION { return Err(Error::UnknownProtocolVersion); } match packet_type { - PACKET_TYPE_INITIAL_KEY_OFFER => { - // alice (remote) -> bob (local) + PACKET_TYPE_ALICE_NOISE_XK_INIT => { + // Alice (remote) --> Bob (local) - //////////////////////////////////////////////////////////////// - // packet decoding for noise initial key offer - // -> e, es, s, ss - //////////////////////////////////////////////////////////////// + /* + * This is the first message Bob receives from Alice, the initiator. It contains + * Alice's ephemeral keys but not her identity. Alice will not reveal her identity + * until forward secrecy is established and she's authenticated Bob. + * + * Bob authenticates the message and confirms that Alice indeed knows Bob's + * identity, then responds with his ephemeral keys. + */ - if kex_packet_len < (HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE + AES_GCM_TAG_SIZE + HMAC_SIZE + HMAC_SIZE) { + if incoming_counter != 1 || session.is_some() || incoming.is_some() { + return Err(Error::OutOfSequence); + } + if pkt_assembled.len() != AliceNoiseXKInit::SIZE { return Err(Error::InvalidPacket); } - let plaintext_end = HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE; - let payload_end = kex_packet_len - (AES_GCM_TAG_SIZE + HMAC_SIZE + HMAC_SIZE); - let aes_gcm_tag_end = kex_packet_len - (HMAC_SIZE + HMAC_SIZE); - let hmac1_end = kex_packet_len - HMAC_SIZE; + // Otherwise parse the packet, authenticate, generate keys, etc. and record state in an + // incoming state object until this phase of the negotiation is done. + let pkt: &AliceNoiseXKInit = byte_array_as_proto_buffer(pkt_assembled)?; + let alice_noise_e = P384PublicKey::from_bytes(&pkt.alice_noise_e).ok_or(Error::FailedAuthentication)?; + let noise_es = app.get_local_s_keypair().agree(&alice_noise_e).ok_or(Error::FailedAuthentication)?; - // Check the secondary HMAC first, which proves that the sender knows the recipient's full static identity. + // Authenticate packet and also prove that Alice knows our static public key. if !secure_eq( + &pkt.hmac_es, &hmac_sha384_2( - app.get_local_s_public_blob_hash(), - &message_nonce, - &kex_packet[HEADER_SIZE..hmac1_end], + kbkdf::(noise_es.as_bytes()).as_bytes(), + &incoming_message_nonce, + &pkt_assembled[HEADER_SIZE..AliceNoiseXKInit::AUTH_START], ), - &kex_packet[hmac1_end..kex_packet_len], ) { return Err(Error::FailedAuthentication); } - // Check rate limits. - if let Some(session) = session.as_ref() { - if (session.state.read().unwrap().last_remote_offer + Application::REKEY_RATE_LIMIT_MS) > current_time { - return Err(Error::RateLimited); - } - } else { - if !app.check_new_session(self, remote_address) { - return Err(Error::RateLimited); - } + // Let application filter incoming connection attempt by whatever criteria it wants. + if !check_allow_incoming_session() { + return Ok(ReceiveResult::Rejected); } - // Key agreement: alice (remote) ephemeral NIST P-384 <> local static NIST P-384 - let alice_e_public = - P384PublicKey::from_bytes(&kex_packet[(HEADER_SIZE + 1)..plaintext_end]).ok_or(Error::FailedAuthentication)?; - let noise_es = app - .get_local_s_keypair() - .agree(&alice_e_public) - .ok_or(Error::FailedAuthentication)?; - - // Initial key derivation from starting point, mixing in alice's ephemeral public and the es. - let noise_ik_incomplete_es = Secret(hmac_sha512( - &hmac_sha512(&INITIAL_KEY, alice_e_public.as_bytes()), - noise_es.as_bytes(), - )); - - // Decrypt the encrypted part of the packet payload and authenticate the above key exchange via AES-GCM auth. - let mut c = AesGcm::new( - kbkdf512(noise_ik_incomplete_es.as_bytes(), KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB).first_n::(), - false, + // Decrypt encrypted part of payload. + aes_ctr_crypt_one_time_use_key( + kbkdf::(noise_es.as_bytes()).as_bytes(), + &mut pkt_assembled[AliceNoiseXKInit::ENC_START..AliceNoiseXKInit::AUTH_START], ); - c.reset_init_gcm(&message_nonce); - c.crypt_in_place(&mut kex_packet[plaintext_end..payload_end]); - let gcm_tag = &kex_packet[payload_end..aes_gcm_tag_end]; - if !c.finish_decrypt(gcm_tag) { - return Err(Error::FailedAuthentication); - } - // Parse payload and get alice's session ID, alice's public blob, metadata, and (if present) Alice's Kyber1024 public. - let ( - offer_id, - alice_session_id, - alice_s_public_blob, - alice_metadata, - alice_hk_public_raw, - alice_ratchet_key_fingerprint, - ) = parse_dec_key_offer_after_header(&kex_packet[plaintext_end..kex_packet_len], packet_type)?; + let pkt: &AliceNoiseXKInit = byte_array_as_proto_buffer(pkt_assembled)?; + let alice_session_id = SessionId::new_from_bytes(&pkt.alice_session_id).ok_or(Error::InvalidPacket)?; + let header_protection_key = Secret(pkt.header_protection_key); - // We either have a session, in which case they should have supplied a ratchet key fingerprint, or - // we don't and they should not have supplied one. - if session.is_some() != alice_ratchet_key_fingerprint.is_some() { - return Err(Error::FailedAuthentication); - } + // Create Bob's ephemeral keys and derive noise_es_ee by agreeing with Alice's. Also create + // a Kyber ciphertext to send back to Alice. + let bob_noise_e_secret = P384KeyPair::generate(); + let bob_noise_e = bob_noise_e_secret.public_key_bytes().clone(); + let noise_es_ee = Secret(hmac_sha512( + noise_es.as_bytes(), + bob_noise_e_secret.agree(&alice_noise_e).ok_or(Error::FailedAuthentication)?.as_bytes(), + )); + let (bob_hk_ciphertext, hk) = pqc_kyber::encapsulate(&pkt.alice_hk_public, &mut random::SecureRandom::default()) + .map_err(|_| Error::FailedAuthentication) + .map(|(ct, hk)| (ct, Secret(hk)))?; - // Extract alice's static NIST P-384 public key from her public blob. - let alice_s_public = Application::extract_s_public_from_raw(alice_s_public_blob).ok_or(Error::InvalidPacket)?; + let mut sessions = self.sessions.write().unwrap(); - // Key agreement: both sides' static P-384 keys. - let noise_ss = app - .get_local_s_keypair() - .agree(&alice_s_public) - .ok_or(Error::FailedAuthentication)?; - - // Mix result of 'ss' agreement into master key. - let noise_ik_incomplete_es_ss = Secret(hmac_sha512(noise_ik_incomplete_es.as_bytes(), noise_ss.as_bytes())); - drop(noise_ik_incomplete_es); - - // Authenticate entire packet with HMAC-SHA384, verifying alice's identity via 'ss' secret that was - // just mixed into the key. - if !secure_eq( - &hmac_sha384_2( - kbkdf512(noise_ik_incomplete_es_ss.as_bytes(), KBKDF_KEY_USAGE_LABEL_HMAC).first_n::<48>(), - &message_nonce, - &kex_packet_saved_ciphertext[HEADER_SIZE..aes_gcm_tag_end], - ), - &kex_packet[aes_gcm_tag_end..hmac1_end], - ) { - return Err(Error::FailedAuthentication); - } - - // Alice's offer has been verified and her current key state reconstructed. - - // Perform checks and match ratchet key if there's an existing session, or gate (via host) and - // then create new sessions. - let (new_session, ratchet_key, last_ratchet_count) = if let Some(session) = session.as_ref() { - // Existing session identity must match the one in this offer. - if !secure_eq(&session.remote_s_public_blob_hash, &SHA384::hash(&alice_s_public_blob)) { - return Err(Error::FailedAuthentication); + let mut bob_session_id; + loop { + bob_session_id = SessionId::random(); + if !sessions.active.contains_key(&bob_session_id) && !sessions.incoming.contains_key(&bob_session_id) { + break; } + } - // Match ratchet key fingerprint and fail if no match, which likely indicates an old offer packet. - let alice_ratchet_key_fingerprint = alice_ratchet_key_fingerprint.unwrap(); - let mut ratchet_key = None; - let mut last_ratchet_count = 0; - let state = session.state.read().unwrap(); - for k in state.session_keys.iter() { - if let Some(k) = k.as_ref() { - if public_fingerprint_of_secret(k.ratchet_key.as_bytes())[..16].eq(alice_ratchet_key_fingerprint) { - ratchet_key = Some(k.ratchet_key.clone()); - last_ratchet_count = k.ratchet_count; - break; - } + // If this queue is too big, we remove the latest entry and replace it. The latest + // is used because under flood conditions this is most likely to be another bogus + // entry. If we find one that is actually timed out, that one is replaced instead. + if sessions.incoming.len() >= self.max_incomplete_session_queue_size { + let mut newest = i64::MIN; + let mut replace_id = None; + let cutoff_time = current_time - Application::INCOMING_SESSION_NEGOTIATION_TIMEOUT_MS; + for (id, s) in sessions.incoming.iter() { + if s.timestamp <= cutoff_time { + replace_id = Some(*id); + break; + } else if s.timestamp >= newest { + newest = s.timestamp; + replace_id = Some(*id); } } - if ratchet_key.is_none() { - return Ok(ReceiveResult::Ignored); // old packet? - } - - (None, ratchet_key, last_ratchet_count) - } else { - if let Some((new_session_id, psk, associated_object)) = - app.accept_new_session(self, remote_address, alice_s_public_blob, alice_metadata) - { - let header_check_cipher = Aes::new( - kbkdf512(noise_ss.as_bytes(), KBKDF_KEY_USAGE_LABEL_HEADER_CHECK).first_n::(), - ); - ( - Some(Session:: { - id: new_session_id, - application_data: associated_object, - receive_window: std::array::from_fn(|_| AtomicU64::new(0)), - send_counter: AtomicU64::new(1), - psk, - noise_ss, - header_check_cipher, - state: RwLock::new(SessionMutableState { - remote_session_id: Some(alice_session_id), - session_keys: [None, None], - cur_session_key_idx: 0, - offer: None, - last_remote_offer: current_time, - }), - remote_s_public_blob_hash: SHA384::hash(&alice_s_public_blob), - remote_s_public_p384_bytes: alice_s_public.as_bytes().clone(), - defrag: Mutex::new(RingBufferMap::new(random::xorshift64_random() as u32)), - }), - None, - 0, - ) - } else { - return Err(Error::NewSessionRejected); - } - }; - - // Set 'session' to a reference to either the existing or the new session. - let existing_session = session; - let session = existing_session.as_ref().map_or_else(|| new_session.as_ref().unwrap(), |s| &*s); - - if !session.update_receive_window(counter) { - return Ok(ReceiveResult::Ignored); + let _ = sessions.incoming.remove(replace_id.as_ref().unwrap()); } - // Generate our ephemeral NIST P-384 key pair. - let bob_e_keypair = P384KeyPair::generate(); - - // Key agreement: both sides' ephemeral P-384 public keys. - let noise_ee = bob_e_keypair.agree(&alice_e_public).ok_or(Error::FailedAuthentication)?; - - // Key agreement: bob (local) static NIST P-384, alice (remote) ephemeral P-384. - let noise_se = bob_e_keypair.agree(&alice_s_public).ok_or(Error::FailedAuthentication)?; - - // Mix in the psk, the key to this point, our ephemeral public, ee, and se, completing Noise_IK. - // - // FIPS note: the order of HMAC parameters are flipped here from the usual Noise HMAC(key, X). That's because - // NIST/FIPS allows HKDF with HMAC(salt, key) and salt is allowed to be anything. This way if the PSK is not - // FIPS compliant the compliance of the entire key derivation is not invalidated. Both inputs are secrets of - // fixed size so this shouldn't matter cryptographically. - let noise_ik_complete = Secret(hmac_sha512( - session.psk.as_bytes(), - &hmac_sha512( - &hmac_sha512( - &hmac_sha512(noise_ik_incomplete_es_ss.as_bytes(), bob_e_keypair.public_key_bytes()), - noise_ee.as_bytes(), - ), - noise_se.as_bytes(), - ), - )); - drop(noise_ik_incomplete_es_ss); - drop(noise_ee); - drop(noise_se); - - // At this point we've completed Noise_IK key derivation with NIST P-384 ECDH, but now for hybrid and ratcheting... - - // Generate a Kyber encapsulated ciphertext if Kyber is enabled and the other side sent us a public key. - let (bob_hk_public, hybrid_kk) = if JEDI && alice_hk_public_raw.len() > 0 { - if let Ok((bob_hk_public, hybrid_kk)) = - pqc_kyber::encapsulate(alice_hk_public_raw, &mut random::SecureRandom::default()) - { - (Some(bob_hk_public), Some(Secret(hybrid_kk))) - } else { - return Err(Error::FailedAuthentication); - } - } else { - (None, None) - }; - - //////////////////////////////////////////////////////////////// - // packet encoding for noise key counter offer - // <- e, ee, se - //////////////////////////////////////////////////////////////// - - let next_ratchet_count = last_ratchet_count + 1; - - let mut reply_buf = [0_u8; KEX_BUF_LEN]; - let reply_counter = session.send_counter.fetch_add(1, Ordering::SeqCst); - let mut idx = HEADER_SIZE; - - idx = safe_write_all(&mut reply_buf, idx, &[SESSION_PROTOCOL_VERSION])?; - idx = safe_write_all(&mut reply_buf, idx, bob_e_keypair.public_key_bytes())?; - let plaintext_end = idx; - - idx = safe_write_all(&mut reply_buf, idx, offer_id)?; - idx = safe_write_all(&mut reply_buf, idx, session.id.as_bytes())?; - idx = varint_safe_write(&mut reply_buf, idx, 0)?; // they don't need our static public; they have it - idx = varint_safe_write(&mut reply_buf, idx, 0)?; // no meta-data in counter-offers (could be used in the future) - if let Some(bob_hk_public) = bob_hk_public.as_ref() { - idx = safe_write_all(&mut reply_buf, idx, &[HYBRID_KEY_TYPE_KYBER1024])?; - idx = safe_write_all(&mut reply_buf, idx, bob_hk_public)?; - } else { - idx = safe_write_all(&mut reply_buf, idx, &[HYBRID_KEY_TYPE_NONE])?; - } - if ratchet_key.is_some() { - idx = safe_write_all(&mut reply_buf, idx, &[0x01])?; - idx = safe_write_all(&mut reply_buf, idx, alice_ratchet_key_fingerprint.unwrap())?; - } else { - idx = safe_write_all(&mut reply_buf, idx, &[0x00])?; - } - let payload_end = idx; - - let reply_message_nonce = create_message_nonce(PACKET_TYPE_KEY_COUNTER_OFFER, reply_counter); - - // Encrypt reply packet using final Noise_IK key BEFORE mixing hybrid or ratcheting, since the other side - // must decrypt before doing these things. - let mut c = AesGcm::new( - kbkdf512(noise_ik_complete.as_bytes(), KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE).first_n::(), - true, + // Reserve session ID on this side and record incomplete session state. + sessions.incoming.insert( + bob_session_id, + Arc::new(IncomingIncompleteSession { + timestamp: current_time, + alice_session_id, + bob_session_id, + noise_es_ee: noise_es_ee.clone(), + hk, + bob_noise_e_secret, + header_protection_key: Secret(pkt.header_protection_key), + }), ); - c.reset_init_gcm(&reply_message_nonce); - c.crypt_in_place(&mut reply_buf[plaintext_end..payload_end]); - let gcm_tag = c.finish_encrypt(); + debug_assert!(!sessions.active.contains_key(&bob_session_id)); - idx = safe_write_all(&mut reply_buf, idx, &gcm_tag)?; - let aes_gcm_tag_end = idx; + drop(sessions); - // Mix ratchet key from previous session key (if any) and Kyber1024 hybrid shared key (if any). - let mut session_key = noise_ik_complete; - if let Some(ratchet_key) = ratchet_key { - session_key = Secret(hmac_sha512(ratchet_key.as_bytes(), session_key.as_bytes())); - } - if let Some(hybrid_kk) = hybrid_kk.as_ref() { - session_key = Secret(hmac_sha512(hybrid_kk.as_bytes(), session_key.as_bytes())); - } + // Create Bob's ephemeral counter-offer reply. + let mut ack_packet = [0u8; BobNoiseXKAck::SIZE]; + let ack: &mut BobNoiseXKAck = byte_array_as_proto_buffer_mut(&mut ack_packet)?; + ack.session_protocol_version = SESSION_PROTOCOL_VERSION; + ack.bob_noise_e = bob_noise_e; + ack.bob_session_id = *bob_session_id.as_bytes(); + ack.bob_hk_ciphertext = bob_hk_ciphertext; - // Authenticate packet using HMAC-SHA384 with final key. Note that while the final key now has the Kyber secret - // mixed in, this doesn't constitute session authentication with Kyber because there's no static Kyber key - // associated with the remote identity. An attacker who can break NIST P-384 (and has the psk) could MITM the - // Kyber exchange, but you'd need a not-yet-existing quantum computer for that. - let hmac = hmac_sha384_2( - kbkdf512(session_key.as_bytes(), KBKDF_KEY_USAGE_LABEL_HMAC).first_n::<48>(), - &reply_message_nonce, - &reply_buf[HEADER_SIZE..aes_gcm_tag_end], - ); - idx = safe_write_all(&mut reply_buf, idx, &hmac)?; - let packet_end = idx; - - let session_key = SessionKey::new( - session_key, - Role::Bob, - current_time, - reply_counter, - next_ratchet_count, - false, // Bob can't know yet if Alice got the counter offer - hybrid_kk.is_some(), + // Encrypt main section of reply. + aes_ctr_crypt_one_time_use_key( + kbkdf::(noise_es_ee.as_bytes()).as_bytes(), + &mut ack_packet[BobNoiseXKAck::ENC_START..BobNoiseXKAck::AUTH_START], ); - let next_key_index = (next_ratchet_count as usize) & 1; - - let mut state = session.state.write().unwrap(); - let _ = state.remote_session_id.replace(alice_session_id); - let _ = state.session_keys[next_key_index].replace(session_key); - state.last_remote_offer = current_time; - drop(state); - - // Bob now has final key state for this exchange. Yay! Now reply to Alice so she can construct it. + // Add HMAC-SHA384 to reply packet. + let reply_hmac = hmac_sha384_2( + kbkdf::(noise_es_ee.as_bytes()).as_bytes(), + &create_message_nonce(PACKET_TYPE_BOB_NOISE_XK_ACK, 1), + &ack_packet[HEADER_SIZE..BobNoiseXKAck::AUTH_START], + ); + ack_packet[BobNoiseXKAck::AUTH_START..].copy_from_slice(&reply_hmac); send_with_fragmentation( - send, - &mut reply_buf[..packet_end], + |b| send(None, b), + &mut ack_packet, mtu, - PACKET_TYPE_KEY_COUNTER_OFFER, - u64::from(alice_session_id), - next_ratchet_count, - reply_counter, - &session.header_check_cipher, + PACKET_TYPE_BOB_NOISE_XK_ACK, + Some(alice_session_id), + 0, + 1, + Some(&Aes::new(header_protection_key.as_bytes())), )?; - if new_session.is_some() { - return Ok(ReceiveResult::OkNewSession(new_session.unwrap())); - } else { - return Ok(ReceiveResult::Ok); - } + return Ok(ReceiveResult::Ok(session)); } - PACKET_TYPE_KEY_COUNTER_OFFER => { - // bob (remote) -> alice (local) + PACKET_TYPE_BOB_NOISE_XK_ACK => { + // Bob (remote) --> Alice (local) - //////////////////////////////////////////////////////////////// - // packet decoding for noise key counter offer - // <- e, ee, se - //////////////////////////////////////////////////////////////// + /* + * This is Bob's reply to Alice's first message, allowing Alice to verify Bob's + * identity. Once this is done Alice can send her identity (encrypted) to complete + * the negotiation. + */ - if kex_packet_len < (HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE + AES_GCM_TAG_SIZE + HMAC_SIZE) { + if incoming_counter != 1 || incoming.is_some() { + return Err(Error::OutOfSequence); + } + if pkt_assembled.len() != BobNoiseXKAck::SIZE { return Err(Error::InvalidPacket); } - let plaintext_end = HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE; - let payload_end = kex_packet_len - (AES_GCM_TAG_SIZE + HMAC_SIZE); - let aes_gcm_tag_end = kex_packet_len - HMAC_SIZE; if let Some(session) = session { let state = session.state.read().unwrap(); - if let Some(offer) = state.offer.as_ref() { - let bob_e_public = P384PublicKey::from_bytes(&kex_packet[(HEADER_SIZE + 1)..plaintext_end]) - .ok_or(Error::FailedAuthentication)?; - let noise_ee = offer.alice_e_keypair.agree(&bob_e_public).ok_or(Error::FailedAuthentication)?; - let noise_se = app.get_local_s_keypair().agree(&bob_e_public).ok_or(Error::FailedAuthentication)?; - let noise_ik_complete = Secret(hmac_sha512( - session.psk.as_bytes(), - &hmac_sha512( - &hmac_sha512(&hmac_sha512(offer.ss_key.as_bytes(), bob_e_public.as_bytes()), noise_ee.as_bytes()), - noise_se.as_bytes(), - ), + // This doesn't make sense if the session is up. + if state.keys[state.current_key].is_some() { + return Err(Error::OutOfSequence); + } + + if let Offer::NoiseXKInit(outgoing_offer) = &state.current_offer { + let pkt: &BobNoiseXKAck = byte_array_as_proto_buffer(pkt_assembled)?; + + // Derive noise_es_ee from Bob's ephemeral public key. + let bob_noise_e = P384PublicKey::from_bytes(&pkt.bob_noise_e).ok_or(Error::FailedAuthentication)?; + let noise_es_ee = Secret(hmac_sha512( + outgoing_offer.noise_es.as_bytes(), + outgoing_offer + .alice_noise_e_secret + .agree(&bob_noise_e) + .ok_or(Error::FailedAuthentication)? + .as_bytes(), )); - drop(noise_ee); - drop(noise_se); - let mut c = AesGcm::new( - kbkdf512(noise_ik_complete.as_bytes(), KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE) - .first_n::(), - false, - ); - c.reset_init_gcm(&message_nonce); - c.crypt_in_place(&mut kex_packet[plaintext_end..payload_end]); - let gcm_tag = &kex_packet[payload_end..aes_gcm_tag_end]; - if !c.finish_decrypt(gcm_tag) { - return Err(Error::FailedAuthentication); - } + let noise_es_ee_kex_hmac_key = + kbkdf::(noise_es_ee.as_bytes()); - // Alice has now completed Noise_IK with NIST P-384 and verified with GCM auth, but now for hybrid... - - let (offer_id, bob_session_id, _, _, bob_hk_public_raw, bob_ratchet_key_id) = - parse_dec_key_offer_after_header(&kex_packet[plaintext_end..kex_packet_len], packet_type)?; - - // Check that this is a counter offer to the original offer we sent. - if !offer.id.eq(offer_id) { - return Ok(ReceiveResult::Ignored); - } - - // Kyber1024 key agreement if enabled. - let hybrid_kk = if JEDI && bob_hk_public_raw.len() > 0 && offer.alice_hk_keypair.is_some() { - if let Ok(hybrid_kk) = - pqc_kyber::decapsulate(bob_hk_public_raw, &offer.alice_hk_keypair.as_ref().unwrap().secret) - { - Some(Secret(hybrid_kk)) - } else { - return Err(Error::FailedAuthentication); - } - } else { - None - }; - - // The session key starts with the final noise_ik key and may have other things mixed into it below. - let mut session_key = noise_ik_complete; - - // Mix ratchet key from previous session key (if any) and Kyber1024 hybrid shared key (if any). - let last_ratchet_count = if bob_ratchet_key_id.is_some() && offer.ratchet_key.is_some() { - session_key = Secret(hmac_sha512(offer.ratchet_key.as_ref().unwrap().as_bytes(), session_key.as_bytes())); - offer.ratchet_count - } else { - 0 - }; - if let Some(hybrid_kk) = hybrid_kk.as_ref() { - session_key = Secret(hmac_sha512(hybrid_kk.as_bytes(), session_key.as_bytes())); - } - - // Check main packet HMAC for full validation of session key. + // Authenticate Bob's reply and the validity of bob_noise_e. if !secure_eq( + &pkt.hmac_es_ee, &hmac_sha384_2( - kbkdf512(session_key.as_bytes(), KBKDF_KEY_USAGE_LABEL_HMAC).first_n::<48>(), - &message_nonce, - &kex_packet_saved_ciphertext[HEADER_SIZE..aes_gcm_tag_end], + noise_es_ee_kex_hmac_key.as_bytes(), + &incoming_message_nonce, + &pkt_assembled[HEADER_SIZE..BobNoiseXKAck::AUTH_START], ), - &kex_packet[aes_gcm_tag_end..kex_packet_len], ) { return Err(Error::FailedAuthentication); } - // Alice has now completed and validated the full hybrid exchange. - - let reply_counter = session.send_counter.fetch_add(1, Ordering::SeqCst); - let next_ratchet_count = last_ratchet_count + 1; - - let session_key = SessionKey::new( - session_key, - Role::Alice, - current_time, - reply_counter, - next_ratchet_count, - true, // Alice knows Bob got the offer - hybrid_kk.is_some(), + // Decrypt encrypted portion of message. + aes_ctr_crypt_one_time_use_key( + kbkdf::(noise_es_ee.as_bytes()).as_bytes(), + &mut pkt_assembled[BobNoiseXKAck::ENC_START..BobNoiseXKAck::AUTH_START], ); + let pkt: &BobNoiseXKAck = byte_array_as_proto_buffer(pkt_assembled)?; - drop(state); - let mut state = session.state.write().unwrap(); + if let Some(bob_session_id) = SessionId::new_from_bytes(&pkt.bob_session_id) { + // Complete Noise_XKpsk3 by mixing in noise_se followed by the PSK. The PSK as far as + // the Noise pattern is concerned is the result of mixing the externally supplied PSK + // with the Kyber1024 shared secret (hk). Kyber is treated as part of the PSK because + // it's an external add-on beyond the Noise spec. + let hk = pqc_kyber::decapsulate(&pkt.bob_hk_ciphertext, outgoing_offer.alice_hk_secret.as_bytes()) + .map_err(|_| Error::FailedAuthentication) + .map(|k| Secret(k))?; + let noise_es_ee_se_hk_psk = Secret(hmac_sha512( + &hmac_sha512( + noise_es_ee.as_bytes(), + app.get_local_s_keypair() + .agree(&bob_noise_e) + .ok_or(Error::FailedAuthentication)? + .as_bytes(), + ), + &hmac_sha512(session.psk.as_bytes(), hk.as_bytes()), + )); - let _ = state.remote_session_id.replace(bob_session_id); - let next_key_index = (next_ratchet_count as usize) & 1; - let _ = state.session_keys[next_key_index].replace(session_key); - state.cur_session_key_idx = next_key_index; - let _ = state.offer.take(); + let reply_message_nonce = create_message_nonce(PACKET_TYPE_ALICE_NOISE_XK_ACK, 2); - return Ok(ReceiveResult::Ok); + // Create reply informing Bob of our static identity now that we've verified Bob and set + // up forward secrecy. Also return Bob's opaque note. + let mut reply_buffer = [0u8; MAX_NOISE_HANDSHAKE_SIZE]; + reply_buffer[HEADER_SIZE] = SESSION_PROTOCOL_VERSION; + let mut reply_len = HEADER_SIZE + 1; + let mut reply_buffer_append = |b: &[u8]| { + let reply_len_new = reply_len + b.len(); + debug_assert!(reply_len_new <= MAX_NOISE_HANDSHAKE_SIZE); + reply_buffer[reply_len..reply_len_new].copy_from_slice(b); + reply_len = reply_len_new; + }; + let alice_s_public_blob = app.get_local_s_public_blob(); + assert!(alice_s_public_blob.len() <= (u16::MAX as usize)); + reply_buffer_append(&(alice_s_public_blob.len() as u16).to_le_bytes()); + reply_buffer_append(alice_s_public_blob); + if let Some(md) = outgoing_offer.metadata.as_ref() { + reply_buffer_append(&(md.len() as u16).to_le_bytes()); + reply_buffer_append(md.as_ref()); + } else { + reply_buffer_append(&[0u8, 0u8]); // no meta-data + } + + // Encrypt Alice's static identity and other inner payload items. The key used here is + // mixed with 'hk' to make identity secrecy PQ forward secure. + aes_ctr_crypt_one_time_use_key( + &hmac_sha512(noise_es_ee.as_bytes(), hk.as_bytes())[..AES_256_KEY_SIZE], + &mut reply_buffer[HEADER_SIZE + 1..reply_len], + ); + + // First attach HMAC allowing Bob to verify that this is from the same Alice and to + // verify the authenticity of encrypted data. + let hmac_es_ee = hmac_sha384_2( + noise_es_ee_kex_hmac_key.as_bytes(), + &reply_message_nonce, + &reply_buffer[HEADER_SIZE..reply_len], + ); + reply_buffer[reply_len..reply_len + HMAC_SHA384_SIZE].copy_from_slice(&hmac_es_ee); + reply_len += HMAC_SHA384_SIZE; + + // Then attach the final HMAC permitting Bob to verify the authenticity of the whole + // key exchange. Bob won't be able to do this until he decrypts and parses Alice's + // identity, so the first HMAC is to let him authenticate that first. + let hmac_es_ee_se_hk_psk = hmac_sha384_2( + kbkdf::(noise_es_ee_se_hk_psk.as_bytes()).as_bytes(), + &reply_message_nonce, + &reply_buffer[HEADER_SIZE..reply_len], + ); + reply_buffer[reply_len..reply_len + HMAC_SHA384_SIZE].copy_from_slice(&hmac_es_ee_se_hk_psk); + reply_len += HMAC_SHA384_SIZE; + + drop(state); + { + 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, 1, current_time, 2, false, false)); + debug_assert!(state.keys[1].is_none()); + state.current_key = 0; + state.current_offer = Offer::NoiseXKAck(Box::new(OutgoingSessionAck { + last_retry_time: AtomicI64::new(current_time), + ack: reply_buffer, + ack_size: reply_len, + })); + } + + send_with_fragmentation( + |b| send(Some(&session), b), + &mut reply_buffer[..reply_len], + mtu, + PACKET_TYPE_ALICE_NOISE_XK_ACK, + Some(bob_session_id), + 0, + 2, + Some(&session.header_protection_cipher), + )?; + + return Ok(ReceiveResult::Ok(Some(session))); + } else { + return Err(Error::InvalidPacket); + } + } else { + return Err(Error::OutOfSequence); } + } else { + return Err(Error::UnknownLocalSessionId); } - - // Just ignore counter-offers that are out of place. They probably indicate that this side - // restarted and needs to establish a new session. - return Ok(ReceiveResult::Ignored); } - _ => return Err(Error::InvalidPacket), + PACKET_TYPE_ALICE_NOISE_XK_ACK => { + // Alice (remote) --> Bob (local) + + /* + * After negotiating a keyed session and Alice has had the opportunity to + * verify Bob, this is when Bob gets to learn who Alice is. At this point + * Bob can make a final decision about whether to keep talking to Alice + * and can create an actual session using the state memo-ized in the memo + * that Alice must return. + */ + + if incoming_counter != 2 || session.is_some() { + return Err(Error::OutOfSequence); + } + if pkt_assembled.len() < ALICE_NOISE_XK_ACK_MIN_SIZE { + return Err(Error::InvalidPacket); + } + + if let Some(incoming) = incoming { + // Check the first HMAC to verify against the currently known noise_es_ee key, which verifies + // that this reply is part of this session. + let auth_start = pkt_assembled.len() - ALICE_NOISE_XK_ACK_AUTH_SIZE; + if !secure_eq( + &pkt_assembled[auth_start..pkt_assembled.len() - HMAC_SHA384_SIZE], + &hmac_sha384_2( + kbkdf::(incoming.noise_es_ee.as_bytes()).as_bytes(), + &incoming_message_nonce, + &pkt_assembled[HEADER_SIZE..auth_start], + ), + ) { + return Err(Error::FailedAuthentication); + } + + // Make a copy of pkt_assembled so we can check the second HMAC against original ciphertext later. + let mut pkt_assembly_buffer_copy = [0u8; MAX_NOISE_HANDSHAKE_SIZE]; + pkt_assembly_buffer_copy[..pkt_assembled.len()].copy_from_slice(pkt_assembled); + + // Decrypt encrypted section so we can finally learn Alice's static identity. + aes_ctr_crypt_one_time_use_key( + &hmac_sha512(incoming.noise_es_ee.as_bytes(), incoming.hk.as_bytes())[..AES_256_KEY_SIZE], + &mut pkt_assembled[ALICE_NOISE_XK_ACK_ENC_START..auth_start], + ); + + // Read the static public blob and optional meta-data. + let mut pkt_assembled_ptr = HEADER_SIZE + 1; + let mut pkt_assembled_field_end = pkt_assembled_ptr + 2; + if pkt_assembled_field_end >= pkt_assembled.len() { + return Err(Error::InvalidPacket); + } + let alice_static_public_blob_size = + u16::from_le_bytes(pkt_assembled[pkt_assembled_ptr..pkt_assembled_field_end].try_into().unwrap()) as usize; + pkt_assembled_ptr = pkt_assembled_field_end; + pkt_assembled_field_end = pkt_assembled_ptr + alice_static_public_blob_size; + if pkt_assembled_field_end >= pkt_assembled.len() { + return Err(Error::InvalidPacket); + } + let alice_static_public_blob = &pkt_assembled[pkt_assembled_ptr..pkt_assembled_field_end]; + pkt_assembled_ptr = pkt_assembled_field_end; + pkt_assembled_field_end = pkt_assembled_ptr + 2; + if pkt_assembled_field_end >= pkt_assembled.len() { + return Err(Error::InvalidPacket); + } + let alice_meta_data_size = + u16::from_le_bytes(pkt_assembled[pkt_assembled_ptr..pkt_assembled_field_end].try_into().unwrap()) as usize; + pkt_assembled_ptr = pkt_assembled_field_end; + pkt_assembled_field_end = pkt_assembled_ptr + alice_meta_data_size; + let alice_meta_data = if alice_meta_data_size > 0 { + Some(&pkt_assembled[pkt_assembled_ptr..pkt_assembled_field_end]) + } else { + None + }; + + // Check session acceptance and fish Alice's NIST P-384 static public key out of + // her static public blob. + let check_result = check_accept_session(alice_static_public_blob, alice_meta_data); + if check_result.is_none() { + self.sessions.write().unwrap().incoming.remove(&incoming.bob_session_id); + return Ok(ReceiveResult::Rejected); + } + let (alice_noise_s, psk, application_data) = check_result.unwrap(); + + // Complete Noise_XKpsk3 on Bob's side. + let noise_es_ee_se_hk_psk = Secret(hmac_sha512( + &hmac_sha512( + incoming.noise_es_ee.as_bytes(), + incoming + .bob_noise_e_secret + .agree(&alice_noise_s) + .ok_or(Error::FailedAuthentication)? + .as_bytes(), + ), + &hmac_sha512(psk.as_bytes(), incoming.hk.as_bytes()), + )); + + // Verify the packet using the final key to verify the whole key exchange. + if !secure_eq( + &pkt_assembly_buffer_copy[auth_start + HMAC_SHA384_SIZE..pkt_assembled.len()], + &hmac_sha384_2( + kbkdf::(noise_es_ee_se_hk_psk.as_bytes()).as_bytes(), + &incoming_message_nonce, + &pkt_assembly_buffer_copy[HEADER_SIZE..auth_start + HMAC_SHA384_SIZE], + ), + ) { + return Err(Error::FailedAuthentication); + } + + let session = Arc::new(Session { + id: incoming.bob_session_id, + application_data, + psk, + send_counter: AtomicU64::new(2), // 1 was already used during negotiation + receive_window: std::array::from_fn(|_| AtomicU64::new(0)), + header_protection_cipher: Aes::new(incoming.header_protection_key.as_bytes()), + state: RwLock::new(State { + remote_session_id: Some(incoming.alice_session_id), + keys: [ + Some(SessionKey::new::(noise_es_ee_se_hk_psk, 1, current_time, 2, true, true)), + None, + ], + current_key: 0, + current_offer: Offer::None, + }), + defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), + }); + + { + let mut sessions = self.sessions.write().unwrap(); + sessions.incoming.remove(&incoming.bob_session_id); + sessions.active.insert(incoming.bob_session_id, Arc::downgrade(&session)); + } + + let _ = session.send_nop(|b| send(Some(&session), b)); + + return Ok(ReceiveResult::OkNewSession(session)); + } else { + return Err(Error::UnknownLocalSessionId); + } + } + + PACKET_TYPE_REKEY_INIT => { + if pkt_assembled.len() != RekeyInit::SIZE { + return Err(Error::InvalidPacket); + } + if incoming.is_some() { + return Err(Error::OutOfSequence); + } + + if let Some(session) = session { + let state = session.state.read().unwrap(); + 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: &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; 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)?.get(); + set_packet_header( + &mut reply_buf, + 1, + 0, + PACKET_TYPE_REKEY_ACK, + u64::from(remote_session_id), + state.current_key, + counter, + ); + + 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); + + 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); + + // 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, + false, + )); + + drop(state); + return Ok(ReceiveResult::Ok(Some(session))); + } + } + return Err(Error::FailedAuthentication); + } + } + } + return Err(Error::OutOfSequence); + } else { + return Err(Error::UnknownLocalSessionId); + } + } + + PACKET_TYPE_REKEY_ACK => { + if pkt_assembled.len() != RekeyAck::SIZE { + return Err(Error::InvalidPacket); + } + if incoming.is_some() { + return Err(Error::OutOfSequence); + } + + if let Some(session) = session { + let state = session.state.read().unwrap(); + if let Offer::RekeyInit(alice_e_secret, _) = &state.current_offer { + if let Some(key) = state.keys[key_index].as_ref() { + // Only the current "Bob" initiates rekeys and expects this ACK. + if key.bob { + let mut c = key.get_receive_cipher(); + c.reset_init_gcm(&incoming_message_nonce); + 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: &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(), + alice_e_secret.agree(&bob_e).ok_or(Error::FailedAuthentication)?.as_bytes(), + )); + + 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, + true, + )); + state.current_key = next_key_index; // this is an ACK so it's confirmed + state.current_offer = Offer::None; + + drop(state); + return Ok(ReceiveResult::Ok(Some(session))); + } + } + } + return Err(Error::FailedAuthentication); + } + } + } + return Err(Error::OutOfSequence); + } else { + return Err(Error::UnknownLocalSessionId); + } + } + + _ => { + return Err(Error::InvalidPacket); + } } } } } -/// Create an send an ephemeral offer, populating ret_ephemeral_offer on success. -fn send_ephemeral_offer( - send: &mut SendFunction, - counter: u64, - alice_session_id: SessionId, - bob_session_id: Option, - alice_s_public_blob: &[u8], - alice_metadata: &[u8], - bob_s_public: &P384PublicKey, - bob_s_public_blob_hash: &[u8], - noise_ss: &Secret<48>, - current_key: Option<&SessionKey>, - header_check_cipher: Option<&Aes>, // None to use one based on the recipient's public key for initial contact - mtu: usize, - current_time: i64, - ret_ephemeral_offer: &mut Option, // We want to prevent copying the EphemeralOffer up the stack because it's very big. ret_ephemeral_offer will be overwritten with the returned EphemeralOffer when the call completes. -) -> Result<(), Error> { - // Generate a NIST P-384 pair. - let alice_e_keypair = P384KeyPair::generate(); +impl Session { + /// Send data over the session. + /// + /// * `send` - Function to call to send physical packet(s) + /// * `mtu_sized_buffer` - A writable work buffer whose size also specifies the physical MTU + /// * `data` - Data to send + #[inline] + pub fn send(&self, mut send: SendFunction, mtu_sized_buffer: &mut [u8], mut data: &[u8]) -> Result<(), Error> { + debug_assert!(mtu_sized_buffer.len() >= MIN_TRANSPORT_MTU); + let state = self.state.read().unwrap(); + if let Some(remote_session_id) = state.remote_session_id { + if let Some(session_key) = state.keys[state.current_key].as_ref() { + let counter = self.get_next_outgoing_counter().ok_or(Error::MaxKeyLifetimeExceeded)?.get(); - // Perform key agreement with the other side's static P-384 public key. - let noise_es = alice_e_keypair.agree(bob_s_public).ok_or(Error::InvalidPacket)?; + let mut c = session_key.get_send_cipher(counter)?; + c.reset_init_gcm(&create_message_nonce(PACKET_TYPE_DATA, counter)); - // Generate a Kyber1024 (hybrid PQ crypto) pair if enabled. - let alice_hk_keypair = if JEDI { - Some(pqc_kyber::keypair(&mut random::SecureRandom::get())) - } else { - None - }; + let fragment_count = (((data.len() + AES_GCM_TAG_SIZE) as f32) / (mtu_sized_buffer.len() - HEADER_SIZE) as f32).ceil() as usize; + let fragment_max_chunk_size = mtu_sized_buffer.len() - HEADER_SIZE; + let last_fragment_no = fragment_count - 1; - // Get ratchet key for current key if one exists. - let (ratchet_key, ratchet_count) = if let Some(current_key) = current_key { - (Some(current_key.ratchet_key.clone()), current_key.ratchet_count) - } else { - (None, 0) - }; + for fragment_no in 0..fragment_count { + let chunk_size = fragment_max_chunk_size.min(data.len()); + let mut fragment_size = chunk_size + HEADER_SIZE; - // Random ephemeral offer ID - let id: [u8; 16] = random::get_bytes_secure(); + set_packet_header( + mtu_sized_buffer, + fragment_count, + fragment_no, + PACKET_TYPE_DATA, + u64::from(remote_session_id), + state.current_key, + counter, + ); - //////////////////////////////////////////////////////////////// - // packet encoding for noise initial key offer and for noise rekeying - // -> e, es, s, ss - //////////////////////////////////////////////////////////////// + c.crypt(&data[..chunk_size], &mut mtu_sized_buffer[HEADER_SIZE..fragment_size]); + data = &data[chunk_size..]; - // Create ephemeral offer packet (not fragmented yet). - let mut packet_buf = [0_u8; 4096]; - let mut idx = HEADER_SIZE; + if fragment_no == last_fragment_no { + debug_assert!(data.is_empty()); + let tagged_fragment_size = fragment_size + AES_GCM_TAG_SIZE; + mtu_sized_buffer[fragment_size..tagged_fragment_size].copy_from_slice(&c.finish_encrypt()); + fragment_size = tagged_fragment_size; + } - idx = safe_write_all(&mut packet_buf, idx, &[SESSION_PROTOCOL_VERSION])?; - //TODO: check this, the below line is supposed to be the blob, not just the key, right? - idx = safe_write_all(&mut packet_buf, idx, alice_e_keypair.public_key_bytes())?; - let plaintext_end = idx; + self.header_protection_cipher + .encrypt_block_in_place(&mut mtu_sized_buffer[HEADER_PROTECT_ENCRYPT_START..HEADER_PROTECT_ENCRYPT_END]); + send(&mut mtu_sized_buffer[..fragment_size]); + } + debug_assert!(data.is_empty()); - idx = safe_write_all(&mut packet_buf, idx, &id)?; - idx = safe_write_all(&mut packet_buf, idx, alice_session_id.as_bytes())?; - idx = varint_safe_write(&mut packet_buf, idx, alice_s_public_blob.len() as u64)?; - idx = safe_write_all(&mut packet_buf, idx, alice_s_public_blob)?; - idx = varint_safe_write(&mut packet_buf, idx, alice_metadata.len() as u64)?; - idx = safe_write_all(&mut packet_buf, idx, alice_metadata)?; - if let Some(hkp) = alice_hk_keypair { - idx = safe_write_all(&mut packet_buf, idx, &[HYBRID_KEY_TYPE_KYBER1024])?; - idx = safe_write_all(&mut packet_buf, idx, &hkp.public)?; - } else { - idx = safe_write_all(&mut packet_buf, idx, &[HYBRID_KEY_TYPE_NONE])?; + session_key.return_send_cipher(c); + + return Ok(()); + } + } + return Err(Error::SessionNotEstablished); } - if let Some(ratchet_key) = ratchet_key.as_ref() { - idx = safe_write_all(&mut packet_buf, idx, &[0x01])?; - idx = safe_write_all(&mut packet_buf, idx, &public_fingerprint_of_secret(ratchet_key.as_bytes())[..16])?; - } else { - idx = safe_write_all(&mut packet_buf, idx, &[0x00])?; + + /// Send a NOP to the other side (e.g. for keep alive). + pub fn send_nop(&self, mut send: SendFunction) -> Result<(), Error> { + let state = self.state.read().unwrap(); + if let Some(remote_session_id) = state.remote_session_id { + if let Some(session_key) = state.keys[state.current_key].as_ref() { + let counter = self.get_next_outgoing_counter().ok_or(Error::MaxKeyLifetimeExceeded)?.get(); + let mut nop = [0u8; HEADER_SIZE + AES_GCM_TAG_SIZE]; + let mut c = session_key.get_send_cipher(counter)?; + c.reset_init_gcm(&create_message_nonce(PACKET_TYPE_NOP, counter)); + nop[HEADER_SIZE..].copy_from_slice(&c.finish_encrypt()); + session_key.return_send_cipher(c); + set_packet_header(&mut nop, 1, 0, PACKET_TYPE_NOP, u64::from(remote_session_id), state.current_key, counter); + self.header_protection_cipher + .encrypt_block_in_place(&mut nop[HEADER_PROTECT_ENCRYPT_START..HEADER_PROTECT_ENCRYPT_END]); + send(&mut nop); + } + } + return Err(Error::SessionNotEstablished); } - let payload_end = idx; - // Create ephemeral agreement secret. - let es_key = Secret(hmac_sha512( - &hmac_sha512(&INITIAL_KEY, alice_e_keypair.public_key_bytes()), - noise_es.as_bytes(), - )); + /// Check whether this session is established. + pub fn established(&self) -> bool { + let state = self.state.read().unwrap(); + state.keys[state.current_key].as_ref().map_or(false, |k| k.confirmed) + } - let bob_session_id = bob_session_id.map_or(0u64, |i| u64::from(i)); + /// 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 + } + } - let message_nonce = create_message_nonce(PACKET_TYPE_INITIAL_KEY_OFFER, counter); + /// Send a rekey init message. + /// + /// This is called from the session context's service() method when it's time to rekey. + /// It should only be called when the current key was established in the 'bob' role. This + /// is checked when rekey time is checked. + fn initiate_rekey(&self, mut send: SendFunction, current_time: i64) { + let rekey_e = P384KeyPair::generate(); - // Encrypt packet and attach AES-GCM tag. - let gcm_tag = { - let mut c = AesGcm::new( - kbkdf512(es_key.as_bytes(), KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB).first_n::(), - true, - ); - c.reset_init_gcm(&message_nonce); - c.crypt_in_place(&mut packet_buf[plaintext_end..payload_end]); - c.finish_encrypt() - }; + 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(); - idx = safe_write_all(&mut packet_buf, idx, &gcm_tag)?; - let aes_gcm_tag_end = idx; + let state = self.state.read().unwrap(); + if let Some(remote_session_id) = state.remote_session_id { + 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_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); - // Mix in static secret. - let ss_key = Secret(hmac_sha512(es_key.as_bytes(), noise_ss.as_bytes())); - drop(es_key); + debug_assert!(rekey_buf.len() <= MIN_TRANSPORT_MTU); + set_packet_header( + &mut rekey_buf, + 1, + 0, + PACKET_TYPE_REKEY_INIT, + u64::from(remote_session_id), + state.current_key, + counter.get(), + ); - // HMAC packet using static + ephemeral key. - let hmac1 = hmac_sha384_2( - kbkdf512(ss_key.as_bytes(), KBKDF_KEY_USAGE_LABEL_HMAC).first_n::<48>(), - &message_nonce, - &packet_buf[HEADER_SIZE..aes_gcm_tag_end], - ); - idx = safe_write_all(&mut packet_buf, idx, &hmac1)?; - let hmac1_end = idx; + drop(state); - // Add secondary HMAC to verify that the caller knows the recipient's full static public identity. - let hmac2 = hmac_sha384_2(bob_s_public_blob_hash, &message_nonce, &packet_buf[HEADER_SIZE..hmac1_end]); - idx = safe_write_all(&mut packet_buf, idx, &hmac2)?; - let packet_end = idx; + self.header_protection_cipher + .encrypt_block_in_place(&mut rekey_buf[HEADER_PROTECT_ENCRYPT_START..HEADER_PROTECT_ENCRYPT_END]); + send(&mut rekey_buf); - let mut init_header_check_cipher_tmp = None; - send_with_fragmentation( - send, - &mut packet_buf[..packet_end], - mtu, - PACKET_TYPE_INITIAL_KEY_OFFER, - bob_session_id, - ratchet_count, - counter, - header_check_cipher.unwrap_or_else(|| { - init_header_check_cipher_tmp = Some(Aes::new( - kbkdf512(&bob_s_public_blob_hash, KBKDF_KEY_USAGE_LABEL_HEADER_CHECK).first_n::(), - )); - init_header_check_cipher_tmp.as_ref().unwrap() - }), - )?; + self.state.write().unwrap().current_offer = Offer::RekeyInit(rekey_e, current_time); + } + } + } + } + } - *ret_ephemeral_offer = Some(EphemeralOffer { - id, - creation_time: current_time, - ratchet_count, - ratchet_key, - ss_key, - alice_e_keypair, - alice_hk_keypair, - }); + /// Get the next outgoing counter value. + #[inline(always)] + fn get_next_outgoing_counter(&self) -> Option { + NonZeroU64::new(self.send_counter.fetch_add(1, Ordering::SeqCst)) + } - Ok(()) + /// Check the receive window without mutating state. + #[inline(always)] + fn check_receive_window(&self, counter: u64) -> bool { + let prev_counter = self.receive_window[(counter as usize) % COUNTER_WINDOW_MAX_OOO].load(Ordering::Acquire); + prev_counter < counter && counter.wrapping_sub(prev_counter) < COUNTER_WINDOW_MAX_SKIP_AHEAD + } + + /// Update the receive window, returning true if the packet is still valid. + /// This should only be called after the packet is authenticated. + #[inline(always)] + fn update_receive_window(&self, counter: u64) -> bool { + let prev_counter = self.receive_window[(counter as usize) % COUNTER_WINDOW_MAX_OOO].fetch_max(counter, Ordering::AcqRel); + prev_counter < counter && counter.wrapping_sub(prev_counter) < COUNTER_WINDOW_MAX_SKIP_AHEAD + } } +#[inline(always)] fn set_packet_header( packet: &mut [u8], fragment_count: usize, fragment_no: usize, packet_type: u8, - recipient_session_id: u64, - ratchet_count: u64, + remote_session_id: u64, + key_index: usize, counter: u64, -) -> Result<(), Error> { +) { debug_assert!(packet.len() >= MIN_PACKET_SIZE); debug_assert!(fragment_count > 0); + debug_assert!(fragment_count <= MAX_FRAGMENTS); debug_assert!(fragment_no < MAX_FRAGMENTS); debug_assert!(packet_type <= 0x0f); // packet type is 4 bits - if fragment_count <= MAX_FRAGMENTS { - // [0-47] recipient session ID - // -- start of header check cipher single block encrypt -- - // [48-48] key index (least significant bit of ratchet count) - // [49-51] packet type (0-15) - // [52-57] fragment count (1..64 - 1, so 0 means 1 fragment) - // [58-63] fragment number (0..63) - // [64-127] 64-bit counter - memory::store_raw( - (u64::from(recipient_session_id) - | (ratchet_count & 1).wrapping_shl(48) - | (packet_type as u64).wrapping_shl(49) - | ((fragment_count - 1) as u64).wrapping_shl(52) - | (fragment_no as u64).wrapping_shl(58)) - .to_le(), - packet, - ); - memory::store_raw(counter.to_le(), &mut packet[8..]); - Ok(()) - } else { - unlikely_branch(); - Err(Error::DataTooLarge) - } + + // [0-47] recipient session ID + // -- start of header check cipher single block encrypt -- + // [48-48] key index (least significant bit) + // [49-51] packet type (0-15) + // [52-57] fragment count (1..64 - 1, so 0 means 1 fragment) + // [58-63] fragment number (0..63) + // [64-127] 64-bit counter + assert!(packet.len() >= 16); + packet[0..8].copy_from_slice( + &(remote_session_id + | ((key_index & 1) as u64).wrapping_shl(48) + | (packet_type as u64).wrapping_shl(49) + | ((fragment_count - 1) as u64).wrapping_shl(52) + | (fragment_no as u64).wrapping_shl(58)) + .to_le_bytes(), + ); + packet[8..16].copy_from_slice(&counter.to_le_bytes()); } -#[derive(Clone, Copy)] -#[repr(C, packed)] -struct MessageNonce(u64, u32); - -/// Create a 12-bit AES-GCM nonce. -/// -/// The primary information that we want to be contained here is the counter and the -/// packet type. The former makes this unique and the latter's inclusion authenticates -/// it as effectively AAD. Other elements of the header are either not authenticated, -/// like fragmentation info, or their authentication is implied via key exchange like -/// the session ID. -/// -/// This is also used as part of HMAC authentication for key exchange packets. #[inline(always)] -fn create_message_nonce(packet_type: u8, counter: u64) -> [u8; 12] { - memory::to_byte_array(MessageNonce(counter.to_le(), (packet_type as u32).to_le())) +fn parse_packet_header(incoming_packet: &[u8]) -> (usize, u8, u8, u8, u64) { + let raw_header_a = u16::from_le_bytes(incoming_packet[6..8].try_into().unwrap()); + ( + (raw_header_a & 1) as usize, + (raw_header_a.wrapping_shr(1) & 7) as u8, + ((raw_header_a.wrapping_shr(4) & 63) + 1) as u8, + raw_header_a.wrapping_shr(10) as u8, + u64::from_le_bytes(incoming_packet[8..16].try_into().unwrap()), + ) } /// Break a packet into fragments and send them all. +/// /// The contents of packet[] are mangled during this operation, so it should be discarded after. +/// This is only used for key exchange and control packets. For data packets this is done inline +/// for better performance with encryption and fragmentation happening at the same time. fn send_with_fragmentation( - send: &mut SendFunction, + mut send: SendFunction, packet: &mut [u8], mtu: usize, packet_type: u8, - recipient_session_id: u64, - ratchet_count: u64, + remote_session_id: Option, + key_index: usize, counter: u64, - header_check_cipher: &Aes, + header_protect_cipher: Option<&Aes>, ) -> Result<(), Error> { let packet_len = packet.len(); + let recipient_session_id = remote_session_id.map_or(SessionId::NONE, |s| u64::from(s)); let fragment_count = ((packet_len as f32) / (mtu as f32)).ceil() as usize; let mut fragment_start = 0; let mut fragment_end = packet_len.min(mtu); @@ -1311,10 +1542,12 @@ fn send_with_fragmentation( fragment_no, packet_type, recipient_session_id, - ratchet_count, + key_index, counter, - )?; - header_check_cipher.encrypt_block_in_place(&mut fragment[6..22]); + ); + if let Some(hcc) = header_protect_cipher { + hcc.encrypt_block_in_place(&mut fragment[HEADER_PROTECT_ENCRYPT_START..HEADER_PROTECT_ENCRYPT_END]); + } send(fragment); fragment_start = fragment_end - HEADER_SIZE; fragment_end = (fragment_start + mtu).min(packet_len); @@ -1322,79 +1555,63 @@ fn send_with_fragmentation( Ok(()) } -/// Parse KEY_OFFER and KEY_COUNTER_OFFER starting after the unencrypted public key part. -fn parse_dec_key_offer_after_header( - incoming_packet: &[u8], - packet_type: u8, -) -> Result<(&[u8], SessionId, &[u8], &[u8], &[u8], Option<&[u8]>), Error> { - let mut p = &incoming_packet[..]; - let offer_id = safe_read_exact(&mut p, 16)?; - - let mut session_id_buf = 0_u64.to_ne_bytes(); - session_id_buf[..SESSION_ID_SIZE].copy_from_slice(safe_read_exact(&mut p, SESSION_ID_SIZE)?); - let alice_session_id = SessionId::new_from_u64_le(u64::from_ne_bytes(session_id_buf)).ok_or(Error::InvalidPacket)?; - - let alice_s_public_blob_len = varint_safe_read(&mut p)?; - let alice_s_public_blob = safe_read_exact(&mut p, alice_s_public_blob_len as usize)?; - - let alice_metadata_len = varint_safe_read(&mut p)?; - let alice_metadata = safe_read_exact(&mut p, alice_metadata_len as usize)?; - - let alice_hk_public_raw = match safe_read_exact(&mut p, 1)?[0] { - HYBRID_KEY_TYPE_KYBER1024 => { - if packet_type == PACKET_TYPE_INITIAL_KEY_OFFER { - safe_read_exact(&mut p, pqc_kyber::KYBER_PUBLICKEYBYTES)? - } else { - safe_read_exact(&mut p, pqc_kyber::KYBER_CIPHERTEXTBYTES)? - } +/// Assemble a series of fragments into a buffer and return the length of the assembled packet in bytes. +/// +/// This is also only used for key exchange and control packets. For data packets decryption and assembly +/// happen in one pass for better performance. +fn assemble_fragments_into(fragments: &[A::IncomingPacketBuffer], d: &mut [u8]) -> Result { + let mut l = 0; + for i in 0..fragments.len() { + let mut ff = fragments[i].as_ref(); + if ff.len() <= MIN_PACKET_SIZE { + return Err(Error::InvalidPacket); } - _ => &[], - }; - - if p.is_empty() { - return Err(Error::InvalidPacket); + if i > 0 { + ff = &ff[HEADER_SIZE..]; + } + let j = l + ff.len(); + if j > d.len() { + return Err(Error::InvalidPacket); + } + d[l..j].copy_from_slice(ff); + l = j; } - let alice_ratchet_key_fingerprint = if safe_read_exact(&mut p, 1)?[0] == 0x01 { - Some(safe_read_exact(&mut p, 16)?) - } else { - None - }; - - Ok(( - offer_id, //always 16 bytes - alice_session_id, - alice_s_public_blob, - alice_metadata, - alice_hk_public_raw, - alice_ratchet_key_fingerprint, //always 16 bytes - )) + return Ok(l); } impl SessionKey { - /// Create a new symmetric shared session key and set its key expiration times, etc. - fn new(key: Secret<64>, role: Role, current_time: i64, current_counter: u64, ratchet_count: u64, confirmed: bool, jedi: bool) -> Self { - let a2b: Secret = kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB).first_n_clone(); - let b2a: Secret = kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE).first_n_clone(); - let (receive_key, send_key) = match role { - Role::Alice => (b2a, a2b), - Role::Bob => (a2b, b2a), + fn new( + key: Secret, + ratchet_count: u64, + current_time: i64, + current_counter: u64, + bob: bool, + confirmed: bool, + ) -> Self { + let a2b = kbkdf::(key.as_bytes()); + let b2a = kbkdf::(key.as_bytes()); + let (receive_key, send_key) = if bob { + (a2b, b2a) + } else { + (b2a, a2b) }; Self { - ratchet_count, - rekey_at_time: current_time - .checked_add(REKEY_AFTER_TIME_MS + ((random::xorshift64_random() as u32) % REKEY_AFTER_TIME_MS_MAX_JITTER) as i64) - .unwrap(), - rekey_at_counter: current_counter.checked_add(REKEY_AFTER_USES).unwrap(), - expire_at_counter: current_counter.checked_add(EXPIRE_AFTER_USES).unwrap(), - secret_fingerprint: public_fingerprint_of_secret(key.as_bytes())[..16].try_into().unwrap(), - ratchet_key: kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_RATCHETING), + ratchet_key: kbkdf::(key.as_bytes()), receive_key, send_key, receive_cipher_pool: Mutex::new(Vec::with_capacity(2)), send_cipher_pool: Mutex::new(Vec::with_capacity(2)), - role, + rekey_at_time: current_time + .checked_add( + Application::REKEY_AFTER_TIME_MS + ((random::xorshift64_random() as u32) % Application::REKEY_AFTER_TIME_MS_MAX_JITTER) as i64, + ) + .unwrap(), + 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, confirmed, - jedi, } } @@ -1407,8 +1624,6 @@ impl SessionKey { .pop() .unwrap_or_else(|| Box::new(AesGcm::new(self.send_key.as_bytes(), true)))) } else { - unlikely_branch(); - // Not only do we return an error, but we also destroy the key. let mut scp = self.send_cipher_pool.lock().unwrap(); scp.clear(); @@ -1435,46 +1650,6 @@ impl SessionKey { } } -/// Write src into buffer starting at the index idx. If buffer cannot fit src at that location, nothing at all is written and Error::UnexpectedBufferOverrun is returned. No other errors can be returned by this function. An idx incremented by the amount written is returned. -fn safe_write_all(buffer: &mut [u8], idx: usize, src: &[u8]) -> Result { - let dest = &mut buffer[idx..]; - let amt = src.len(); - if dest.len() >= amt { - dest[..amt].copy_from_slice(src); - Ok(idx + amt) - } else { - unlikely_branch(); - Err(Error::UnexpectedBufferOverrun) - } -} - -/// Write a variable length integer, which can consume up to 10 bytes. Uses safe_write_all to do so. -fn varint_safe_write(buffer: &mut [u8], idx: usize, v: u64) -> Result { - let mut b = [0_u8; varint::VARINT_MAX_SIZE_BYTES]; - let i = varint::encode(&mut b, v); - safe_write_all(buffer, idx, &b[0..i]) -} - -/// Read exactly amt bytes from src and return the slice those bytes reside in. If src is smaller than amt, Error::InvalidPacket is returned. if the read was successful src is incremented to start at the first unread byte. -fn safe_read_exact<'a>(src: &mut &'a [u8], amt: usize) -> Result<&'a [u8], Error> { - if src.len() >= amt { - let (a, b) = src.split_at(amt); - *src = b; - Ok(a) - } else { - unlikely_branch(); - Err(Error::InvalidPacket) - } -} - -/// Read a variable length integer, which can consume up to 10 bytes. Uses varint_safe_read to do so. -fn varint_safe_read(src: &mut &[u8]) -> Result { - let (v, amt) = varint::decode(*src).ok_or(Error::InvalidPacket)?; - let (_, b) = src.split_at(amt); - *src = b; - Ok(v) -} - /// Shortcut to HMAC data split into two slices. fn hmac_sha384_2(key: &[u8], a: &[u8], b: &[u8]) -> [u8; 48] { let mut hmac = HMACSHA384::new(key); @@ -1483,16 +1658,45 @@ fn hmac_sha384_2(key: &[u8], a: &[u8], b: &[u8]) -> [u8; 48] { hmac.finish() } -/// HMAC-SHA512 key derivation based on: https://csrc.nist.gov/publications/detail/sp/800-108/final (page 12) -/// Cryptographically this isn't meaningfully different from HMAC(key, [label]) but this is how NIST rolls. -fn kbkdf512(key: &[u8], label: u8) -> Secret<64> { - Secret(hmac_sha512(key, &[0, 0, 0, 0, b'Z', b'T', label, 0, 0, 0, 0, 0x02, 0x00])) +/// Shortcut to AES-CTR encrypt or decrypt with a zero IV. +/// +/// This is used during Noise_XK handshaking. Each stage uses a different key to encrypt the +/// payload that is used only once per handshake and per session. +fn aes_ctr_crypt_one_time_use_key(key: &[u8], data: &mut [u8]) { + let mut ctr = AesCtr::new(key); + ctr.reset_set_iv(&[0u8; 12]); + ctr.crypt_in_place(data); } -/// Get a hash of a secret that can be used as a public key fingerprint to check ratcheting during key exchange. -fn public_fingerprint_of_secret(key: &[u8]) -> [u8; 48] { - let mut tmp = SHA384::new(); - tmp.update(&[0xf0, 0x0d]); // arbitrary salt - tmp.update(key); - tmp.finish() +/// HMAC-SHA512 key derivation based on: https://csrc.nist.gov/publications/detail/sp/800-108/final (page 7) +/// Cryptographically this isn't meaningfully different from HMAC(key, [label]) but this is how NIST rolls. +fn kbkdf(key: &[u8]) -> Secret { + //These are the values we have assigned to the 5 variables involved in https://csrc.nist.gov/publications/detail/sp/800-108/final: + // K_in = key, i = 0x01, Label = 'Z'||'T'||LABEL, Context = 0x00, L = (OUTPUT_BYTES * 8) + Secret::::from_bytes( + &hmac_sha512( + key, + &[ + 1, + b'Z', + b'T', + LABEL, + 0x00, + 0, + (((OUTPUT_BYTES * 8) >> 8) & 0xff) as u8, + ((OUTPUT_BYTES * 8) & 0xff) as u8, + ], + )[..OUTPUT_BYTES], + ) +} + +fn prng32(mut x: u32) -> u32 { + // based on lowbias32 from https://nullprogram.com/blog/2018/07/31/ + x = x.wrapping_add(1); // don't get stuck on 0 + x ^= x.wrapping_shr(16); + x = x.wrapping_mul(0x7feb352d); + x ^= x.wrapping_shr(15); + x = x.wrapping_mul(0x846ca68b); + x ^= x.wrapping_shr(16); + x }