diff --git a/zerotier-core-crypto/src/sidhp751/LICENSE b/attic/sidhp751/LICENSE similarity index 100% rename from zerotier-core-crypto/src/sidhp751/LICENSE rename to attic/sidhp751/LICENSE diff --git a/zerotier-core-crypto/src/sidhp751/ORIGINAL-README.md b/attic/sidhp751/ORIGINAL-README.md similarity index 100% rename from zerotier-core-crypto/src/sidhp751/ORIGINAL-README.md rename to attic/sidhp751/ORIGINAL-README.md diff --git a/zerotier-core-crypto/src/sidhp751/constants.rs b/attic/sidhp751/constants.rs similarity index 100% rename from zerotier-core-crypto/src/sidhp751/constants.rs rename to attic/sidhp751/constants.rs diff --git a/zerotier-core-crypto/src/sidhp751/curve.rs b/attic/sidhp751/curve.rs similarity index 100% rename from zerotier-core-crypto/src/sidhp751/curve.rs rename to attic/sidhp751/curve.rs diff --git a/zerotier-core-crypto/src/sidhp751/field.rs b/attic/sidhp751/field.rs similarity index 100% rename from zerotier-core-crypto/src/sidhp751/field.rs rename to attic/sidhp751/field.rs diff --git a/zerotier-core-crypto/src/sidhp751/fp.rs b/attic/sidhp751/fp.rs similarity index 100% rename from zerotier-core-crypto/src/sidhp751/fp.rs rename to attic/sidhp751/fp.rs diff --git a/zerotier-core-crypto/src/sidhp751/isogeny.rs b/attic/sidhp751/isogeny.rs similarity index 100% rename from zerotier-core-crypto/src/sidhp751/isogeny.rs rename to attic/sidhp751/isogeny.rs diff --git a/zerotier-core-crypto/src/sidhp751/mod.rs b/attic/sidhp751/mod.rs similarity index 100% rename from zerotier-core-crypto/src/sidhp751/mod.rs rename to attic/sidhp751/mod.rs diff --git a/zerotier-core-crypto/src/sidhp751/sidh.rs b/attic/sidhp751/sidh.rs similarity index 100% rename from zerotier-core-crypto/src/sidhp751/sidh.rs rename to attic/sidhp751/sidh.rs diff --git a/zerotier-core-crypto/src/kbkdf.rs b/zerotier-core-crypto/src/kbkdf.rs index 64dd8a231..4b10e3326 100644 --- a/zerotier-core-crypto/src/kbkdf.rs +++ b/zerotier-core-crypto/src/kbkdf.rs @@ -8,9 +8,10 @@ use crate::secret::Secret; +// HMAC'd message is: preface | iteration[4], preface[2], label, 0x00, context, hash size[4] +// See: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-108.pdf page 12 + pub fn zt_kbkdf_hmac_sha384(key: &[u8], label: u8, context: u8, iter: u32) -> Secret<48> { - // HMAC'd message is: preface | iteration[4], preface[2], label, 0x00, context, hash size[4] - // See: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-108.pdf page 12 Secret(crate::hash::hmac_sha384(key, &[ (iter >> 24) as u8, (iter >> 16) as u8, @@ -25,8 +26,6 @@ pub fn zt_kbkdf_hmac_sha384(key: &[u8], label: u8, context: u8, iter: u32) -> Se } pub fn zt_kbkdf_hmac_sha512(key: &[u8], label: u8, context: u8, iter: u32) -> Secret<64> { - // HMAC'd message is: preface | iteration[4], preface[2], label, 0x00, context, hash size[4] - // See: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-108.pdf page 12 Secret(crate::hash::hmac_sha512(key, &[ (iter >> 24) as u8, (iter >> 16) as u8, diff --git a/zerotier-core-crypto/src/lib.rs b/zerotier-core-crypto/src/lib.rs index c312366e8..59229997c 100644 --- a/zerotier-core-crypto/src/lib.rs +++ b/zerotier-core-crypto/src/lib.rs @@ -16,7 +16,6 @@ pub mod random; pub mod secret; pub mod hex; pub mod varint; -pub mod sidhp751; pub use aes_gmac_siv; pub use rand_core; diff --git a/zerotier-core-crypto/src/random.rs b/zerotier-core-crypto/src/random.rs index c3f327c8f..385fc9028 100644 --- a/zerotier-core-crypto/src/random.rs +++ b/zerotier-core-crypto/src/random.rs @@ -6,10 +6,34 @@ * https://www.zerotier.com/ */ +use std::mem::MaybeUninit; use openssl::rand::rand_bytes; pub struct SecureRandom; +#[inline(always)] +pub fn next_u32_secure() -> u32 { + unsafe { + let mut tmp: [u32; 1] = MaybeUninit::uninit().assume_init(); + assert!(rand_bytes(&mut *(tmp.as_mut_ptr().cast::<[u8; 4]>())).is_ok()); + tmp[0] + } +} + +#[inline(always)] +pub fn next_u64_secure() -> u64 { + unsafe { + let mut tmp: [u64; 1] = MaybeUninit::uninit().assume_init(); + assert!(rand_bytes(&mut *(tmp.as_mut_ptr().cast::<[u8; 8]>())).is_ok()); + tmp[0] + } +} + +#[inline(always)] +pub fn fill_bytes_secure(dest: &mut [u8]) { + assert!(rand_bytes(dest).is_ok()); +} + impl SecureRandom { #[inline(always)] pub fn get() -> Self { Self } @@ -17,23 +41,13 @@ impl SecureRandom { impl rand_core::RngCore for SecureRandom { #[inline(always)] - fn next_u32(&mut self) -> u32 { - let mut tmp = 0_u32; - assert!(rand_bytes(unsafe { &mut *(&mut tmp as *mut u32).cast::<[u8; 4]>() }).is_ok()); - tmp - } + fn next_u32(&mut self) -> u32 { next_u32_secure() } #[inline(always)] - fn next_u64(&mut self) -> u64 { - let mut tmp = 0_u64; - assert!(rand_bytes(unsafe { &mut *(&mut tmp as *mut u64).cast::<[u8; 8]>() }).is_ok()); - tmp - } + fn next_u64(&mut self) -> u64 { next_u64_secure() } #[inline(always)] - fn fill_bytes(&mut self, dest: &mut [u8]) { - assert!(rand_bytes(dest).is_ok()); - } + fn fill_bytes(&mut self, dest: &mut [u8]) { fill_bytes_secure(dest); } #[inline(always)] fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { @@ -43,24 +57,7 @@ impl rand_core::RngCore for SecureRandom { impl rand_core::CryptoRng for SecureRandom {} -#[inline(always)] -pub fn next_u32_secure() -> u32 { - let mut tmp = 0_u32; - assert!(rand_bytes(unsafe { &mut *(&mut tmp as *mut u32).cast::<[u8; 4]>() }).is_ok()); - tmp -} - -#[inline(always)] -pub fn next_u64_secure() -> u64 { - let mut tmp = 0_u64; - assert!(rand_bytes(unsafe { &mut *(&mut tmp as *mut u64).cast::<[u8; 8]>() }).is_ok()); - tmp -} - -#[inline(always)] -pub fn fill_bytes_secure(dest: &mut [u8]) { - assert!(rand_bytes(dest).is_ok()); -} +unsafe impl Send for SecureRandom {} static mut XORSHIFT64_STATE: u64 = 0; diff --git a/zerotier-core-crypto/src/secret.rs b/zerotier-core-crypto/src/secret.rs index aa18b87a2..abf70229a 100644 --- a/zerotier-core-crypto/src/secret.rs +++ b/zerotier-core-crypto/src/secret.rs @@ -6,6 +6,7 @@ * https://www.zerotier.com/ */ +use std::mem::MaybeUninit; use std::ptr::write_volatile; /// Container for secrets that clears them on drop. @@ -18,6 +19,7 @@ use std::ptr::write_volatile; /// but it's still not a bad idea due to things like swap or obscure side channel /// attacks that allow memory to be read. #[derive(Clone, PartialEq, Eq)] +#[repr(transparent)] pub struct Secret(pub [u8; L]); impl Secret { @@ -30,13 +32,21 @@ impl Secret { #[inline(always)] pub fn as_bytes(&self) -> &[u8; L] { return &self.0 } + + /// Get a clone of the first N bytes of this secret. + #[inline(always)] + pub fn first_n(&self) -> Secret { + let mut tmp: Secret = unsafe { MaybeUninit::uninit().assume_init() }; + tmp.0.copy_from_slice(&self.0[..N]); + tmp + } } impl Drop for Secret { fn drop(&mut self) { unsafe { for i in 0..L { - write_volatile(self.0.as_mut_ptr().offset(i as isize), 0_u8); + write_volatile(self.0.as_mut_ptr().add(i), 0_u8); } } } diff --git a/zerotier-network-hypervisor/src/vl1/ephemeral.rs b/zerotier-network-hypervisor/src/vl1/ephemeral.rs deleted file mode 100644 index 22f85f48b..000000000 --- a/zerotier-network-hypervisor/src/vl1/ephemeral.rs +++ /dev/null @@ -1,300 +0,0 @@ -/* 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)2021 ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -use std::sync::atomic::{AtomicU32, Ordering}; -use std::io::Write; - -use zerotier_core_crypto::c25519::{C25519KeyPair, C25519_PUBLIC_KEY_SIZE}; -use zerotier_core_crypto::hash::*; -use zerotier_core_crypto::kbkdf::zt_kbkdf_hmac_sha512; -use zerotier_core_crypto::p384::*; -use zerotier_core_crypto::random::SecureRandom; -use zerotier_core_crypto::secret::Secret; -use zerotier_core_crypto::sidhp751::{SIDHPublicKeyAlice, SIDHPublicKeyBob, SIDHSecretKeyAlice, SIDHSecretKeyBob, SIDH_P751_PUBLIC_KEY_SIZE}; -use zerotier_core_crypto::varint; - -use crate::vl1::Address; -use crate::vl1::protocol::*; -use crate::vl1::symmetricsecret::SymmetricSecret; - -pub const ALGORITHM_C25519: u8 = 0x01; -pub const ALGORITHM_NISTP384ECDH: u8 = 0x02; -pub const ALGORITHM_SIDHP751: u8 = 0x04; - -/// A set of ephemeral secret key pairs and related information. -pub(crate) struct EphemeralKeyPairSet { - next_key: Secret<64>, - c25519: C25519KeyPair, - p384: P384KeyPair, - sidhp751: Option, - previous_cumulative_algorithms: u8, -} - -impl EphemeralKeyPairSet { - /// Create a new ephemeral set of secret/public key pairs. - /// - /// This contains key pairs for the asymmetric key agreement algorithms used and a - /// timestamp used to enforce TTL. - /// - /// The key and cumulative algorithms should come from the current in-effect ephemeral secret or - /// should be the long-term static secret and zero if there isn't one. - /// - /// SIDH is slow, so it's only included in the exchange if it's never been included in any previous - /// exchange. The threat model here is long-term data warehousing in anticipation of QC, so one SIDH - /// per ephemeral session is probably good enough for that. - pub fn new(local_address: Address, remote_address: Address, key: &SymmetricSecret, cumulative_algorithms: u8) -> Self { - Self { - next_key: zt_kbkdf_hmac_sha512(key.key.as_bytes(), KBKDF_KEY_USAGE_LABEL_EPHEMERAL_RATCHET_NEXT_KEY, 0, 0), - c25519: C25519KeyPair::generate(), - p384: P384KeyPair::generate(), - sidhp751: if (cumulative_algorithms & ALGORITHM_SIDHP751) == 0 { - Some(SIDHEphemeralKeyPair::generate(local_address, remote_address)) - } else { - None - }, - previous_cumulative_algorithms: cumulative_algorithms - } - } - - /// Create a public version of this ephemeral secret to share with our counterparty. - /// - /// Note that the public key bundle is NOT self-signed or otherwise self-authenticating. It must - /// be transmitted over an authenticated channel. - pub fn public_bytes(&self) -> Vec { - let mut b: Vec = Vec::with_capacity(C25519_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + SIDH_P751_PUBLIC_KEY_SIZE + 8); - - b.push(ALGORITHM_C25519); - let _ = varint::write(&mut b, C25519_PUBLIC_KEY_SIZE as u64); - let _ = b.write_all(&self.c25519.public_bytes()); - - if let Some(sidhp751) = self.sidhp751.as_ref() { - b.push(ALGORITHM_SIDHP751); - let _ = varint::write(&mut b, (SIDH_P751_PUBLIC_KEY_SIZE + 1) as u64); - b.push(sidhp751.role()); - let pk = match &sidhp751 { - SIDHEphemeralKeyPair::Alice(a, _) => a.to_bytes(), - SIDHEphemeralKeyPair::Bob(b, _) => b.to_bytes() - }; - let _ = b.write_all(&pk); - } - - // FIPS note: any FIPS compliant ciphers must be last or the exchange will not be FIPS compliant. That's - // because we chain/ratchet using KHDF and non-FIPS ciphers are considered "salt" inputs for HKDF from a - // FIPS point of view. Final key must be HKDF(salt, a FIPS-compliant algorithm secret). There is zero - // actual security implication to the order. - - b.push(ALGORITHM_NISTP384ECDH); - let _ = varint::write(&mut b, P384_PUBLIC_KEY_SIZE as u64); - let _ = b.write_all(self.p384.public_key_bytes()); - - b - } - - /// Perform ephemeral key agreement. - /// - /// Since ephemeral secrets should only be used once, this consumes the object. - #[allow(non_snake_case)] - pub fn agree(self, time_ticks: i64, mut other_public_bytes: &[u8]) -> Option { - let mut algorithms_successful: u8 = 0; - let mut fips_compliant_exchange = false; - let mut key: Secret<64> = self.next_key.clone(); - - while !other_public_bytes.is_empty() { - let algorithms = other_public_bytes[0]; - other_public_bytes = &other_public_bytes[1..]; - let key_len = varint::read(&mut other_public_bytes); - if key_len.is_err() { - return None; - } - let key_len = key_len.unwrap().0 as usize; - - if algorithms == ALGORITHM_C25519 { - if other_public_bytes.len() < C25519_PUBLIC_KEY_SIZE || key_len != C25519_PUBLIC_KEY_SIZE { - return None; - } - - let c25519_secret = self.c25519.agree(&other_public_bytes[0..C25519_PUBLIC_KEY_SIZE]); - other_public_bytes = &other_public_bytes[C25519_PUBLIC_KEY_SIZE..]; - - key.0 = hmac_sha512(&key.0, &c25519_secret.0); - algorithms_successful |= ALGORITHM_C25519; - fips_compliant_exchange = false; - } else if algorithms == ALGORITHM_SIDHP751 { - if other_public_bytes.len() < (SIDH_P751_PUBLIC_KEY_SIZE + 1) || key_len != (SIDH_P751_PUBLIC_KEY_SIZE + 1) { - return None; - } - - let _ = match self.sidhp751.as_ref() { - Some(SIDHEphemeralKeyPair::Alice(_, seck)) => { - if other_public_bytes[0] != 0 { // Alice can't agree with Alice - Some(Secret(seck.shared_secret(&SIDHPublicKeyBob::from_bytes(&other_public_bytes[1..(SIDH_P751_PUBLIC_KEY_SIZE + 1)])))) - } else { - None - } - }, - Some(SIDHEphemeralKeyPair::Bob(_, seck)) => { - if other_public_bytes[0] != 1 { // Bob can't agree with Bob - Some(Secret(seck.shared_secret(&SIDHPublicKeyAlice::from_bytes(&other_public_bytes[1..(SIDH_P751_PUBLIC_KEY_SIZE + 1)])))) - } else { - None - } - }, - None => None, - }.map(|sidh_secret| { - key.0 = hmac_sha512(&key.0, &sidh_secret.0); - algorithms_successful |= ALGORITHM_SIDHP751; - fips_compliant_exchange = false; - }); - other_public_bytes = &other_public_bytes[(SIDH_P751_PUBLIC_KEY_SIZE + 1)..]; - } else if algorithms == ALGORITHM_NISTP384ECDH { - if other_public_bytes.len() < P384_PUBLIC_KEY_SIZE || key_len != P384_PUBLIC_KEY_SIZE { - return None; - } - - let p384_public = P384PublicKey::from_bytes(&other_public_bytes[0..P384_PUBLIC_KEY_SIZE]); - other_public_bytes = &other_public_bytes[P384_PUBLIC_KEY_SIZE..]; - if p384_public.is_none() { - return None; - } - - let _ = self.p384.agree(p384_public.as_ref().unwrap()).map(|p384_key| { - key.0 = hmac_sha512(&key.0, &p384_key.0); - algorithms_successful |= ALGORITHM_NISTP384ECDH; - fips_compliant_exchange = true; - }); - } else { - if other_public_bytes.len() < key_len { - return None; - } - other_public_bytes = &other_public_bytes[key_len..]; - } - } - - return if algorithms_successful != 0 { - Some(EphemeralSymmetricSecret { - secret: SymmetricSecret::new(key), - rekey_time: time_ticks + EPHEMERAL_SECRET_REKEY_AFTER_TIME, - expire_time: time_ticks + EPHEMERAL_SECRET_REJECT_AFTER_TIME, - encrypt_uses: AtomicU32::new(0), - decrypt_uses: AtomicU32::new(0), - algorithms: algorithms_successful, - cumulative_algorithms: algorithms_successful | self.previous_cumulative_algorithms, - fips_compliant_exchange - }) - } else { - None - }; - } -} - -/// An ephemeral symmetric secret with usage timers and counters. -pub(crate) struct EphemeralSymmetricSecret { - /// Current ephemeral secret key. - pub secret: SymmetricSecret, - /// Time at or after which we should start trying to re-key. - pub rekey_time: i64, - /// Time after which this key is no longer valid. - pub expire_time: i64, - /// Number of times this secret has been used to encrypt. - pub encrypt_uses: AtomicU32, - /// Number of times this secret has been used to decrypt. - pub decrypt_uses: AtomicU32, - /// Algorithms used in this exchange (bit mask). - pub algorithms: u8, - /// Cumulative algorithm mask including previous exchange algorithms. - pub cumulative_algorithms: u8, - /// True if most recent key exchange was NIST/FIPS compliant. - pub fips_compliant_exchange: bool, -} - -impl EphemeralSymmetricSecret { - #[inline(always)] - pub fn should_rekey(&self, time_ticks: i64) -> bool { - time_ticks >= self.rekey_time || self.encrypt_uses.load(Ordering::Relaxed).max(self.decrypt_uses.load(Ordering::Relaxed)) >= EPHEMERAL_SECRET_REKEY_AFTER_USES - } - - #[inline(always)] - pub fn expired(&self, time_ticks: i64) -> bool { - time_ticks >= self.expire_time || self.encrypt_uses.load(Ordering::Relaxed).max(self.decrypt_uses.load(Ordering::Relaxed)) >= EPHEMERAL_SECRET_REJECT_AFTER_USES - } -} - -/// Internal container for SIDH key pairs tracking whether this is the "alice" or "bob" side in the SIDH protocol. -enum SIDHEphemeralKeyPair { - Alice(SIDHPublicKeyAlice, SIDHSecretKeyAlice), - Bob(SIDHPublicKeyBob, SIDHSecretKeyBob) -} - -impl SIDHEphemeralKeyPair { - /// Generate a SIDH key pair. - /// - /// SIDH is weird. A key exchange must involve one participant taking a role - /// canonically called Alice and the other wearing the Bob hat, because math. - /// - /// If our local address is less than the remote address, we take the Alice role. - /// Otherwise if it's greater or equal we take the Bob role. - /// - /// Everything works as long as the two sides take opposite roles. There is no - /// security implication in one side always taking one role. - pub fn generate(local_address: Address, remote_address: Address) -> SIDHEphemeralKeyPair { - let mut rng = SecureRandom::get(); - if local_address < remote_address { - let (p, s) = zerotier_core_crypto::sidhp751::generate_alice_keypair(&mut rng); - SIDHEphemeralKeyPair::Alice(p, s) - } else { - let (p, s) = zerotier_core_crypto::sidhp751::generate_bob_keypair(&mut rng); - SIDHEphemeralKeyPair::Bob(p, s) - } - } - - /// Returns 0 if Alice, 1 if Bob. - #[inline(always)] - pub fn role(&self) -> u8 { - match self { - Self::Alice(_, _) => 0, - Self::Bob(_, _) => 1, - } - } -} - -#[cfg(test)] -mod tests { - use crate::vl1::ephemeral::EphemeralKeyPairSet; - use crate::vl1::Address; - use crate::vl1::symmetricsecret::SymmetricSecret; - use zerotier_core_crypto::secret::Secret; - - #[test] - fn ratchet() { - let static_secret = SymmetricSecret::new(Secret([1_u8; 64])); - let alice_address = Address::from_u64(0xdeadbeef00).unwrap(); - let bob_address = Address::from_u64(0xbeefdead00).unwrap(); - let mut alice = EphemeralKeyPairSet::new(alice_address, bob_address, &static_secret, 0); - let mut bob = EphemeralKeyPairSet::new(bob_address, alice_address, &static_secret, 0); - let ratchets = 16; - let mut alice_cumulative_algorithms: u8 = 0; - let mut bob_cumulative_algorithms: u8 = 0; - for t in 1..ratchets+1 { - let alice_public = alice.public_bytes(); - let bob_public = bob.public_bytes(); - let alice_key = alice.agree(t, bob_public.as_slice()); - let bob_key = bob.agree(t, alice_public.as_slice()); - assert!(alice_key.is_some()); - assert!(bob_key.is_some()); - let alice_key = alice_key.unwrap(); - let bob_key = bob_key.unwrap(); - alice_cumulative_algorithms |= alice_key.cumulative_algorithms; - bob_cumulative_algorithms |= bob_key.cumulative_algorithms; - assert_eq!(&alice_key.secret.key.0, &bob_key.secret.key.0); - alice = EphemeralKeyPairSet::new(alice_address, bob_address, &alice_key.secret, alice_key.cumulative_algorithms); - bob = EphemeralKeyPairSet::new(bob_address, alice_address, &alice_key.secret, alice_key.cumulative_algorithms); - } - assert_ne!(alice_cumulative_algorithms, 0); - assert_ne!(bob_cumulative_algorithms, 0); - } -} diff --git a/zerotier-network-hypervisor/src/vl1/mod.rs b/zerotier-network-hypervisor/src/vl1/mod.rs index a768c74f8..c62272d0e 100644 --- a/zerotier-network-hypervisor/src/vl1/mod.rs +++ b/zerotier-network-hypervisor/src/vl1/mod.rs @@ -20,7 +20,6 @@ pub(crate) mod address; pub(crate) mod mac; pub(crate) mod fragmentedpacket; pub(crate) mod whoisqueue; -pub(crate) mod ephemeral; pub(crate) mod symmetricsecret; pub use address::Address; diff --git a/zerotier-network-hypervisor/src/vl1/peer.rs b/zerotier-network-hypervisor/src/vl1/peer.rs index fa36770cc..4d4197050 100644 --- a/zerotier-network-hypervisor/src/vl1/peer.rs +++ b/zerotier-network-hypervisor/src/vl1/peer.rs @@ -9,7 +9,6 @@ use std::convert::TryInto; use std::mem::MaybeUninit; use std::num::NonZeroI64; - use std::sync::Arc; use std::sync::atomic::{AtomicI64, AtomicU64, AtomicU8, Ordering}; @@ -26,11 +25,10 @@ use crate::{PacketBuffer, VERSION_MAJOR, VERSION_MINOR, VERSION_PROTO, VERSION_R use crate::util::{array_range, u64_as_bytes}; use crate::util::buffer::Buffer; use crate::vl1::{Endpoint, Identity, InetAddress, Path}; -use crate::vl1::ephemeral::EphemeralSymmetricSecret; use crate::vl1::identity::{IDENTITY_ALGORITHM_ALL, IDENTITY_ALGORITHM_X25519}; use crate::vl1::node::*; use crate::vl1::protocol::*; -use crate::vl1::symmetricsecret::SymmetricSecret; +use crate::vl1::symmetricsecret::{EphemeralSymmetricSecret, SymmetricSecret}; /// A remote peer known to this node. /// Sending-related and receiving-related fields are locked separately since concurrent diff --git a/zerotier-network-hypervisor/src/vl1/protocol.rs b/zerotier-network-hypervisor/src/vl1/protocol.rs index 941c8de05..a6904e7d9 100644 --- a/zerotier-network-hypervisor/src/vl1/protocol.rs +++ b/zerotier-network-hypervisor/src/vl1/protocol.rs @@ -39,7 +39,7 @@ pub const KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K0: u8 = b'0'; pub const KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K1: u8 = b'1'; /// KBKDF usage label for the key used to advance the ratchet. -pub const KBKDF_KEY_USAGE_LABEL_EPHEMERAL_RATCHET_NEXT_KEY: u8 = b'e'; +pub const KBKDF_KEY_USAGE_LABEL_EPHEMERAL_RATCHET_KEY: u8 = b'e'; /// Try to re-key ephemeral keys after this time. pub const EPHEMERAL_SECRET_REKEY_AFTER_TIME: i64 = 300000; // 5 minutes diff --git a/zerotier-network-hypervisor/src/vl1/symmetricsecret.rs b/zerotier-network-hypervisor/src/vl1/symmetricsecret.rs index 0a345e1b4..420bea147 100644 --- a/zerotier-network-hypervisor/src/vl1/symmetricsecret.rs +++ b/zerotier-network-hypervisor/src/vl1/symmetricsecret.rs @@ -6,6 +6,7 @@ * https://www.zerotier.com/ */ +use std::sync::atomic::{AtomicU32, Ordering}; use zerotier_core_crypto::aes_gmac_siv::AesGmacSiv; use zerotier_core_crypto::kbkdf::*; use zerotier_core_crypto::secret::Secret; @@ -14,11 +15,11 @@ use crate::util::pool::{Pool, PoolFactory}; use crate::vl1::protocol::*; /// Pool of reusable AES-GMAC-SIV instances. -pub(crate) struct AesGmacSivPoolFactory(Secret<48>, Secret<48>); +pub(crate) struct AesGmacSivPoolFactory(Secret<32>, Secret<32>); impl PoolFactory for AesGmacSivPoolFactory { #[inline(always)] - fn create(&self) -> AesGmacSiv { AesGmacSiv::new(&self.0.0[0..32], &self.1.0[0..32]) } + fn create(&self) -> AesGmacSiv { AesGmacSiv::new(&self.0.0, &self.1.0) } #[inline(always)] fn reset(&self, obj: &mut AesGmacSiv) { obj.reset(); } @@ -28,13 +29,9 @@ impl PoolFactory for AesGmacSivPoolFactory { /// /// This contains the key and several sub-keys and ciphers keyed with sub-keys. pub(crate) struct SymmetricSecret { - /// The root shared symmetric secret from which other keys are derived. pub key: Secret<64>, - - /// Key for adding an HMAC to packets e.g. in v2+ HELLO. pub packet_hmac_key: Secret<64>, - - /// A pool of reusable keyed and initialized AES-GMAC-SIV ciphers. + pub ephemeral_ratchet_key: Secret<64>, pub aes_gmac_siv: Pool, } @@ -47,15 +44,39 @@ impl Eq for SymmetricSecret {} impl SymmetricSecret { /// Create a new symmetric secret, deriving all sub-keys and such. - pub fn new(base_key: Secret<64>) -> SymmetricSecret { - let usage_packet_hmac = zt_kbkdf_hmac_sha512(&base_key.0, KBKDF_KEY_USAGE_LABEL_PACKET_HMAC, 0, 0); + pub fn new(key: Secret<64>) -> SymmetricSecret { + let packet_hmac_key = zt_kbkdf_hmac_sha512(&key.0, KBKDF_KEY_USAGE_LABEL_PACKET_HMAC, 0, 0); + let ephemeral_ratchet_key = zt_kbkdf_hmac_sha512(&key.0, KBKDF_KEY_USAGE_LABEL_EPHEMERAL_RATCHET_KEY, 0, 0); let aes_factory = AesGmacSivPoolFactory( - zt_kbkdf_hmac_sha384(&base_key.0[0..48], KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K0, 0, 0), - zt_kbkdf_hmac_sha384(&base_key.0[0..48], KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K1, 0, 0)); + zt_kbkdf_hmac_sha384(&key.0[0..48], KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K0, 0, 0).first_n(), + zt_kbkdf_hmac_sha384(&key.0[0..48], KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K1, 0, 0).first_n()); SymmetricSecret { - key: base_key, - packet_hmac_key: usage_packet_hmac, + key, + packet_hmac_key, + ephemeral_ratchet_key, aes_gmac_siv: Pool::new(2, aes_factory), } } } + +/// An ephemeral symmetric secret with usage timers and counters. +pub(crate) struct EphemeralSymmetricSecret { + pub secret: SymmetricSecret, + pub rekey_time: i64, + pub expire_time: i64, + pub encrypt_uses: AtomicU32, + pub decrypt_uses: AtomicU32, + pub fips_compliant_exchange: bool, +} + +impl EphemeralSymmetricSecret { + #[inline(always)] + pub fn should_rekey(&self, time_ticks: i64) -> bool { + time_ticks >= self.rekey_time || self.encrypt_uses.load(Ordering::Relaxed).max(self.decrypt_uses.load(Ordering::Relaxed)) >= EPHEMERAL_SECRET_REKEY_AFTER_USES + } + + #[inline(always)] + pub fn expired(&self, time_ticks: i64) -> bool { + time_ticks >= self.expire_time || self.encrypt_uses.load(Ordering::Relaxed).max(self.decrypt_uses.load(Ordering::Relaxed)) >= EPHEMERAL_SECRET_REJECT_AFTER_USES + } +}