From b335c631a9583f62d150ce9bfb92d8b25b65f266 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Wed, 17 Nov 2021 10:17:23 -0500 Subject: [PATCH] Use a state hash for determining if ephemeral ratchet can advance, and some big perf improvements in SIDH. --- zerotier-core-crypto/src/hash.rs | 2 +- zerotier-core-crypto/src/sidhp751/curve.rs | 12 +- zerotier-core-crypto/src/sidhp751/field.rs | 20 ++- zerotier-core-crypto/src/sidhp751/fp.rs | 58 +-------- .../src/vl1/ephemeral.rs | 121 ++++++++++++------ .../src/vl1/protocol.rs | 6 +- 6 files changed, 105 insertions(+), 114 deletions(-) diff --git a/zerotier-core-crypto/src/hash.rs b/zerotier-core-crypto/src/hash.rs index bc3ba77ee..4d22db3f3 100644 --- a/zerotier-core-crypto/src/hash.rs +++ b/zerotier-core-crypto/src/hash.rs @@ -25,7 +25,7 @@ impl SHA512 { pub fn hmac(key: &[u8], msg: &[u8]) -> [u8; SHA512_HASH_SIZE] { let mut m = gcrypt::mac::Mac::new(gcrypt::mac::Algorithm::HmacSha512).unwrap(); - m.set_key(key).expect("FATAL: invalid HMAC-SHA512 key");; + m.set_key(key).expect("FATAL: invalid HMAC-SHA512 key"); m.update(msg).expect("FATAL: HMAC-SHA512 failed"); let mut h = [0_u8; SHA512_HASH_SIZE]; m.get_mac(&mut h).expect("FATAL: HMAC-SHA512 failed"); diff --git a/zerotier-core-crypto/src/sidhp751/curve.rs b/zerotier-core-crypto/src/sidhp751/curve.rs index f4c75e4ac..556923df1 100644 --- a/zerotier-core-crypto/src/sidhp751/curve.rs +++ b/zerotier-core-crypto/src/sidhp751/curve.rs @@ -9,7 +9,7 @@ //! This module contains internal curve representation and operations //! for SIDH, which is not part of the public API. -use crate::sidhp751::fp::Fp751Element; +use crate::sidhp751::fp::{Fp751Element, FP751_NUM_WORDS}; use crate::sidhp751::field::{PrimeFieldElement, ExtensionFieldElement}; use crate::sidhp751::constants::*; @@ -19,9 +19,7 @@ use subtle::{ConditionallySelectable, Choice}; #[cfg(test)] use quickcheck::{Gen, Arbitrary}; -use std::mem::zeroed; -// Macro to assign tuples, as Rust does not allow tuples as lvalue. macro_rules! assign{ {($v1:ident, $v2:ident) = $e:expr} => { @@ -36,7 +34,7 @@ macro_rules! assign{ // = 256 const CONST_256: ExtensionFieldElement = ExtensionFieldElement { A: Fp751Element([0x249ad67, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7300000, 0x9973da8b, 0x73815496, 0x46718c7f, 0x856657c1, 0xe363a697, 0x461860e4,0xbba838cd, 0xf9fd6510,0x06993c0c, 0x4e1a3c3f, 0xef5b75c7, 0x55ab]), - B: Fp751Element([0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]) + B: Fp751Element([0_u32; FP751_NUM_WORDS]) }; /// A point on the projective line `P^1(F_{p^2})`. @@ -193,8 +191,7 @@ impl Arbitrary for ProjectivePoint { impl ProjectivePoint { /// Creates a new zero `ProejctivePoint`. pub fn new() -> ProjectivePoint { - unsafe { zeroed() } - //ProjectivePoint{ X: ExtensionFieldElement::zero(), Z: ExtensionFieldElement::zero() } + ProjectivePoint{ X: ExtensionFieldElement::zero(), Z: ExtensionFieldElement::zero() } } #[allow(non_snake_case)] @@ -731,8 +728,7 @@ impl Arbitrary for ProjectivePrimeFieldPoint { impl ProjectivePrimeFieldPoint { /// Creates a new zero `ProjectivePrimeFieldPoint`. pub fn new() -> ProjectivePrimeFieldPoint { - unsafe { zeroed() } - //ProjectivePrimeFieldPoint{ X: PrimeFieldElement::zero(), Z: PrimeFieldElement::zero() } + ProjectivePrimeFieldPoint{ X: PrimeFieldElement::zero(), Z: PrimeFieldElement::zero() } } pub fn from_affine(x: &PrimeFieldElement) -> ProjectivePrimeFieldPoint { diff --git a/zerotier-core-crypto/src/sidhp751/field.rs b/zerotier-core-crypto/src/sidhp751/field.rs index cc1121681..e3efae6f0 100644 --- a/zerotier-core-crypto/src/sidhp751/field.rs +++ b/zerotier-core-crypto/src/sidhp751/field.rs @@ -14,7 +14,6 @@ use crate::sidhp751::fp::*; use std::fmt::Debug; use std::cmp::{Eq, PartialEq}; -use std::mem::zeroed; use std::ops::*; use subtle::ConditionallySelectable; @@ -179,18 +178,17 @@ impl Arbitrary for ExtensionFieldElement { impl ExtensionFieldElement { /// Construct a zero `ExtensionFieldElement`. pub fn zero() -> ExtensionFieldElement { - unsafe { zeroed() } - //ExtensionFieldElement{ - // A: Fp751Element([0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]), - // B: Fp751Element([0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]), - //} + ExtensionFieldElement{ + A: Fp751Element::zero(), + B: Fp751Element::zero(), + } } /// Construct a one `ExtensionFieldElement`. pub fn one() -> ExtensionFieldElement { ExtensionFieldElement{ A: Fp751Element([0x249ad, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x83100000, 0x375c6c66, 0x5527b1e4, 0x3f4f24d0, 0x697797bf, 0xac5c4e2e, 0xc89db7b2, 0xd2076956, 0x4ca4b439, 0x7512c7e9, 0x10f7926c, 0x24bce5e2, 0x2d5b]), - B: Fp751Element([0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]), + B: Fp751Element([0_u32; FP751_NUM_WORDS]), } } @@ -406,11 +404,11 @@ impl Arbitrary for PrimeFieldElement { impl PrimeFieldElement { /// Construct a zero `PrimeFieldElement`. + #[inline(always)] pub fn zero() -> PrimeFieldElement { - unsafe { zeroed() } - //PrimeFieldElement{ - // A: Fp751Element([0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]), - //} + PrimeFieldElement{ + A: Fp751Element::zero(), + } } /// Construct a one `PrimeFieldElement`. diff --git a/zerotier-core-crypto/src/sidhp751/fp.rs b/zerotier-core-crypto/src/sidhp751/fp.rs index a502403ac..a2134b785 100644 --- a/zerotier-core-crypto/src/sidhp751/fp.rs +++ b/zerotier-core-crypto/src/sidhp751/fp.rs @@ -8,7 +8,7 @@ use crate::random::SecureRandom; -use std::mem::{size_of, MaybeUninit, zeroed}; +use std::mem::size_of; use std::fmt::Debug; use std::ops::Neg; @@ -18,8 +18,6 @@ use quickcheck::{Arbitrary, Gen}; use subtle::{ConditionallySelectable, Choice}; use rand_core::RngCore; -// Macro to assign tuples, as Rust does not allow tuples as lvalue. -#[macro_export] macro_rules! assign{ {($v1:ident, $v2:expr) = $e:expr} => { @@ -31,7 +29,6 @@ macro_rules! assign{ }; } -// X86 finite field arithmetic const RADIX: u32 = 32; pub const FP751_NUM_WORDS: usize = 24; const P751_ZERO_WORDS: usize = 11; @@ -40,44 +37,11 @@ const P751: [u32; FP751_NUM_WORDS] = [4294967295, 4294967295, 4294967295, 429496 const P751P1: [u32; FP751_NUM_WORDS] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4004511744, 1241020584, 3823933061, 335006838, 3667237658, 3605784694, 139368551, 1555191624, 2237838596, 2545605734, 236097695, 3577870108, 28645]; const P751X2: [u32; FP751_NUM_WORDS] = [4294967294, 4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 3714056191, 2482041169, 3352898826, 670013677, 3039508020, 2916602093, 278737103, 3110383248, 180709896, 796244173, 472195391, 2860772920, 57291]; -fn digit_x_digit(a: u32, b: u32, c: &mut [u32; 2]) { - #[allow(non_upper_case_globals)] - const sizeof_u32: u32 = size_of::() as u32; - #[allow(non_upper_case_globals)] - const mask_low: u32 = ::MAX >> (sizeof_u32 * 4); - #[allow(non_upper_case_globals)] - const mask_high: u32 = ::MAX << (sizeof_u32 * 4); - - let al = a & mask_low; - let ah = a >> (sizeof_u32 * 4); - let bl = b & mask_low; - let bh = b >> (sizeof_u32 * 4); - - let albl = al * bl; - let albh = al * bh; - let ahbl = ah * bl; - let ahbh = ah * bh; - let c0 = albl & mask_low; - - let mut res1 = albl >> (sizeof_u32 * 4); - let mut res2 = ahbl & mask_low; - let mut res3 = albh & mask_low; - let mut temp = res1 + res2 + res3; - let mut carry = temp >> (sizeof_u32 * 4); - c[0] = c0 ^ (temp << (sizeof_u32 * 4)); - - res1 = ahbl >> (sizeof_u32 * 4); - res2 = albh >> (sizeof_u32 * 4); - res3 = ahbh & mask_low; - temp = res1 + res2 + res3 + carry; - let c1 = temp & mask_low; - carry = temp & mask_high; - c[1] = c1 ^ ((ahbh & mask_high) + carry); -} - #[inline(always)] fn mul(multiplier: u32, multiplicant: u32, uv: &mut [u32; 2]) { - digit_x_digit(multiplier, multiplicant, uv); + let p = (multiplier as u64) * (multiplicant as u64); + uv[0] = p as u32; + uv[1] = (p >> 32) as u32; } #[inline(always)] @@ -320,16 +284,14 @@ pub struct Fp751Element(pub (crate) [u32; FP751_NUM_WORDS]); pub struct Fp751ElementDist; impl ConditionallySelectable for Fp751Element { - #[inline(always)] fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { - let mut bytes = unsafe { MaybeUninit::::uninit().assume_init() }; + let mut bytes = Fp751Element::zero(); for i in 0..FP751_NUM_WORDS { bytes.0[i] = u32::conditional_select(&a.0[i], &b.0[i], choice); } bytes } - #[inline(always)] fn conditional_assign(&mut self, f: &Self, choice: Choice) { let mask = ((choice.unwrap_u8() as i32).neg()) as u32; for i in 0..FP751_NUM_WORDS { @@ -376,8 +338,7 @@ impl Fp751Element { /// Construct a new zero `Fp751Element`. #[inline(always)] pub fn zero() -> Fp751Element { - unsafe { zeroed() } - //Fp751Element([0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]) + Fp751Element([0_u32; FP751_NUM_WORDS]) } /// Given an `Fp751Element` in Montgomery form, convert to little-endian bytes. @@ -386,10 +347,8 @@ impl Fp751Element { let mut a = Fp751Element::zero(); #[allow(non_snake_case)] let mut aR = Fp751X2::zero(); - //let one = Fp751Element([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); aR.0[..FP751_NUM_WORDS].clone_from_slice(&self.0); - //aR = self * &one; a = aR.reduce(); // = a mod p in [0, 2p) a = a.strong_reduce(); // = a mod p in [0, p) @@ -399,7 +358,6 @@ impl Fp751Element { for i in 0..94 { j = i / 4; k = (i % 4) as u32; - // Rust indexes are of type usize. bytes[i as usize] = (a.0[j as usize] >> (8 * k)) as u8; } bytes @@ -439,9 +397,7 @@ impl Fp751X2 { // Construct a zero `Fp751X2`. #[inline(always)] pub fn zero() -> Fp751X2 { - unsafe { zeroed() } - //Fp751X2([0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - // 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]) + Fp751X2([0_u32; { 2 * FP751_NUM_WORDS }]) } } diff --git a/zerotier-network-hypervisor/src/vl1/ephemeral.rs b/zerotier-network-hypervisor/src/vl1/ephemeral.rs index 5ad4889e0..0e64e5cc4 100644 --- a/zerotier-network-hypervisor/src/vl1/ephemeral.rs +++ b/zerotier-network-hypervisor/src/vl1/ephemeral.rs @@ -6,7 +6,7 @@ * https://www.zerotier.com/ */ -use std::sync::atomic::AtomicU32; +use std::sync::atomic::{AtomicU32, Ordering}; use std::io::Write; use std::convert::TryInto; @@ -19,47 +19,52 @@ use zerotier_core_crypto::sidhp751::{SIDHPublicKeyAlice, SIDHPublicKeyBob, SIDHS use zerotier_core_crypto::varint; use crate::vl1::Address; -use crate::vl1::protocol::EphemeralKeyAgreementAlgorithm; +use crate::vl1::protocol::{EphemeralKeyAgreementAlgorithm, EPHEMERAL_SECRET_USE_SIDH_EVERY_N_RATCHETS, EPHEMERAL_SECRET_REKEY_AFTER_TIME, EPHEMERAL_SECRET_REKEY_AFTER_USES, EPHEMERAL_SECRET_REJECT_AFTER_TIME}; use crate::vl1::symmetricsecret::SymmetricSecret; -/// An ephemeral secret key negotiated to implement forward secrecy. -pub struct EphemeralSecret { - timestamp_ticks: i64, - ratchet_count: u64, +/// A set of ephemeral secret key pairs. Multiple algorithms are used. +pub struct EphemeralKeyPairSet { + previous_ratchet_state: Option<[u8; 16]>, c25519: C25519KeyPair, p521: P521KeyPair, sidhp751: Option, } -impl EphemeralSecret { - /// Create a new ephemeral secret key. +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. /// - /// SIDH is much slower than Curve25519 and NIST P-521, so it's only included every - /// 256 clicks of the ratchet. The point of SIDH is forward secrecy out to the age - /// of quantum computing in case someone is warehousing traffic today to analyze - /// tomorrow. An attacker from 5-15 years from now will not be able to time travel - /// back in time and steal an ephemeral SIDH secret key with a side channel attack. - pub fn new(time_ticks: i64, local_address: Address, remote_address: Address, previous_ephemeral_secret: Option<&EphemeralSymmetricSecret>) -> Self { - let ratchet_count = previous_ephemeral_secret.map_or(0_u64, |previous_ephemeral_secret| previous_ephemeral_secret.next_ratchet_count); - EphemeralSecret { - timestamp_ticks: time_ticks, - ratchet_count, + /// SIDH is only used the first time since it's slow and its only purpose is to + /// defend against further-future quantum computer attacks. + pub fn new(local_address: Address, remote_address: Address, previous_ephemeral_secret: Option<&EphemeralSymmetricSecret>) -> Self { + let (sidhp751, previous_ratchet_state) = previous_ephemeral_secret.map_or_else(|| { + (Some(SIDHEphemeralKeyPair::generate(local_address, remote_address)), None) + }, |previous_ephemeral_secret| { + (None, Some(previous_ephemeral_secret.ratchet_state.clone())) + }); + EphemeralKeyPairSet { + previous_ratchet_state, c25519: C25519KeyPair::generate(true), p521: P521KeyPair::generate(true).expect("NIST P-521 key pair generation failed"), - sidhp751: if (ratchet_count % 256) == 0 { - Some(SIDHEphemeralKeyPair::generate(local_address, remote_address)) - } else { - None - }, + sidhp751, } } /// 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(8 + C25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + SIDH_P751_PUBLIC_KEY_SIZE); + let mut b: Vec = Vec::with_capacity(SHA384_HASH_SIZE + 8 + C25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + SIDH_P751_PUBLIC_KEY_SIZE); + + self.previous_ratchet_state.as_ref().map_or_else(|| { + b.push(0); // no flags + }, |previous_ratchet_state| { + b.push(1); // flag: previous ephemeral secret hash included + let _ = b.write_all(previous_ratchet_state); + }); b.push(EphemeralKeyAgreementAlgorithm::C25519 as u8); let _ = varint::write(&mut b, C25519_PUBLIC_KEY_SIZE as u64); @@ -78,8 +83,8 @@ impl EphemeralSecret { // 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, FIPS-compliant algorithm secret). Order has no actual - // implication for security. + // 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(EphemeralKeyAgreementAlgorithm::NistP521ECDH as u8); let _ = varint::write(&mut b, P521_PUBLIC_KEY_SIZE as u64); @@ -97,14 +102,36 @@ impl EphemeralSecret { /// /// Since ephemeral secrets should only be used once, this consumes the object. pub fn agree(self, time_ticks: i64, static_secret: &SymmetricSecret, previous_ephemeral_secret: Option<&EphemeralSymmetricSecret>, other_public_bytes: &[u8]) -> Option { - let mut key = previous_ephemeral_secret.map_or_else(|| { - static_secret.next_ephemeral_ratchet_key.clone() + let (mut key, mut c25519_ratchet_count, mut sidhp751_ratchet_count, mut nistp521_ratchet_count) = previous_ephemeral_secret.map_or_else(|| { + (static_secret.next_ephemeral_ratchet_key.clone(), 0, 0, 0) }, |previous_ephemeral_secret| { - Secret(SHA384::hmac(&static_secret.next_ephemeral_ratchet_key.0, &previous_ephemeral_secret.secret.next_ephemeral_ratchet_key.0)) + ( + Secret(SHA384::hmac(&static_secret.next_ephemeral_ratchet_key.0, &previous_ephemeral_secret.secret.next_ephemeral_ratchet_key.0)), + previous_ephemeral_secret.c25519_ratchet_count, + previous_ephemeral_secret.sidhp751_ratchet_count, + previous_ephemeral_secret.nistp512_ratchet_count + ) }); let mut algs: Vec = Vec::with_capacity(3); let mut other_public_bytes = other_public_bytes; + + // Make sure the state of the ratchet matches on both ends. Otherwise it must restart. + if other_public_bytes.is_empty() { + return None; + } + if (other_public_bytes[0] & 1) == 0 { + if previous_ephemeral_secret.is_some() { + return None; + } + other_public_bytes = &other_public_bytes[1..]; + } else { + if other_public_bytes.len() < 17 || previous_ephemeral_secret.map_or(false, |previous_ephemeral_secret| other_public_bytes[1..17].ne(&previous_ephemeral_secret.ratchet_state)) { + return None; + } + other_public_bytes = &other_public_bytes[17..]; + } + while !other_public_bytes.is_empty() { let cipher = other_public_bytes[0]; other_public_bytes = &other_public_bytes[1..]; @@ -124,6 +151,7 @@ impl EphemeralSecret { other_public_bytes = &other_public_bytes[C25519_PUBLIC_KEY_SIZE..]; key.0 = SHA384::hmac(&key.0, &c25519_secret.0); algs.push(EphemeralKeyAgreementAlgorithm::C25519); + c25519_ratchet_count += 1; }, Ok(EphemeralKeyAgreementAlgorithm::SIDHP751) => { @@ -149,6 +177,7 @@ impl EphemeralSecret { }.map(|sidh_secret| { key.0 = SHA384::hmac(&key.0, &sidh_secret.0); algs.push(EphemeralKeyAgreementAlgorithm::SIDHP751); + sidh_ratchet_count += 1; }); other_public_bytes = &other_public_bytes[(SIDH_P751_PUBLIC_KEY_SIZE + 1)..]; }, @@ -168,6 +197,7 @@ impl EphemeralSecret { } key.0 = SHA384::hmac(&key.0, &p521_key.unwrap().0); algs.push(EphemeralKeyAgreementAlgorithm::NistP521ECDH); + nistp521_ratchet_count += 1; }, Err(_) => { @@ -183,10 +213,13 @@ impl EphemeralSecret { return if !algs.is_empty() { Some(EphemeralSymmetricSecret { secret: SymmetricSecret::new(key), + ratchet_state: SHA384::hash(&key.0)[0..16].try_into().unwrap(), agreement_algorithms: algs, - agreement_timestamp_ticks: time_ticks, - local_secret_timestamp_ticks: self.timestamp_ticks, - next_ratchet_count: self.ratchet_count + 1, + rekey_time: time_ticks + EPHEMERAL_SECRET_REKEY_AFTER_TIME, + expire_time: time_ticks + EPHEMERAL_SECRET_REJECT_AFTER_TIME, + c25519_ratchet_count, + sidhp751_ratchet_count, + nistp512_ratchet_count, encrypt_uses: AtomicU32::new(0), decrypt_uses: AtomicU32::new(0) }) @@ -196,12 +229,16 @@ impl EphemeralSecret { } } +/// Symmetric secret representing a step in the ephemeral keying ratchet. pub struct EphemeralSymmetricSecret { secret: SymmetricSecret, + ratchet_state: [u8; 16], agreement_algorithms: Vec, - agreement_timestamp_ticks: i64, - local_secret_timestamp_ticks: i64, - next_ratchet_count: u64, + rekey_time: i64, + expire_time: i64, + c25519_ratchet_count: u64, + sidhp751_ratchet_count: u64, + nistp512_ratchet_count: u64, encrypt_uses: AtomicU32, decrypt_uses: AtomicU32, } @@ -209,19 +246,23 @@ pub struct EphemeralSymmetricSecret { impl EphemeralSymmetricSecret { #[inline(always)] pub fn use_secret_to_encrypt(&self) -> &SymmetricSecret { - let _ = self.encrypt_uses.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + let _ = self.encrypt_uses.fetch_add(1, Ordering::Relaxed); &self.secret } #[inline(always)] pub fn use_secret_to_decrypt(&self) -> &SymmetricSecret { - let _ = self.decrypt_uses.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + let _ = self.decrypt_uses.fetch_add(1, Ordering::Relaxed); &self.secret } pub fn is_fips_compliant(&self) -> bool { self.agreement_algorithms.last().map_or(false, |alg| alg.is_fips_compliant()) } + + 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 + } } #[derive(Copy, Clone)] @@ -264,7 +305,7 @@ impl SIDHEphemeralKeyPair { #[cfg(test)] mod tests { - use crate::vl1::ephemeral::EphemeralSecret; + use crate::vl1::ephemeral::EphemeralKeyPairSet; use crate::vl1::Address; use crate::vl1::symmetricsecret::SymmetricSecret; use zerotier_core_crypto::secret::Secret; @@ -272,8 +313,8 @@ mod tests { #[test] fn ephemeral_agreement() { let static_secret = SymmetricSecret::new(Secret([1_u8; 48])); - let alice = EphemeralSecret::new(1, Address::from_u64(0xdeadbeef00).unwrap(), Address::from_u64(0xbeefdead00).unwrap(), None); - let bob = EphemeralSecret::new(1, Address::from_u64(0xbeefdead00).unwrap(), Address::from_u64(0xdeadbeef00).unwrap(), None); + let alice = EphemeralKeyPairSet::new(Address::from_u64(0xdeadbeef00).unwrap(), Address::from_u64(0xbeefdead00).unwrap(), None); + let bob = EphemeralKeyPairSet::new(Address::from_u64(0xbeefdead00).unwrap(), Address::from_u64(0xdeadbeef00).unwrap(), None); let alice_public_bytes = alice.public_bytes(); let bob_public_bytes = bob.public_bytes(); let alice_key = alice.agree(2, &static_secret, None, bob_public_bytes.as_slice()).unwrap(); diff --git a/zerotier-network-hypervisor/src/vl1/protocol.rs b/zerotier-network-hypervisor/src/vl1/protocol.rs index a6441009d..cfef4aae6 100644 --- a/zerotier-network-hypervisor/src/vl1/protocol.rs +++ b/zerotier-network-hypervisor/src/vl1/protocol.rs @@ -51,16 +51,16 @@ pub const KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K1: u8 = b'1'; pub const KBKDF_KEY_USAGE_LABEL_EPHEMERAL_RATCHET: u8 = b'E'; /// Try to re-key ephemeral keys after this time. -pub const EPHEMERAL_SECRET_REKEY_AFTER_TIME: i64 = 1000 * 60 * 60; // 1 hour +pub const EPHEMERAL_SECRET_REKEY_AFTER_TIME: i64 = 300000; // 5 minutes /// Maximum number of times to use an ephemeral secret before trying to replace it. -pub const EPHEMERAL_SECRET_REKEY_AFTER_USES: u32 = 536870912; // 1/4 the NIST security limit +pub const EPHEMERAL_SECRET_REKEY_AFTER_USES: u32 = 536870912; // 1/4 the NIST/FIPS security bound of 2^31 /// Ephemeral secret reject after time. pub const EPHEMERAL_SECRET_REJECT_AFTER_TIME: i64 = EPHEMERAL_SECRET_REKEY_AFTER_TIME * 2; /// Ephemeral secret reject after uses. -pub const EPHEMERAL_SECRET_REJECT_AFTER_USES: u32 = 2147483648; // NIST security limit +pub const EPHEMERAL_SECRET_REJECT_AFTER_USES: u32 = 2147483648; // NIST/FIPS security bound /// Length of an address in bytes. pub const ADDRESS_SIZE: usize = 5;