From 06bd77946bdf05d9bf29d26eccac66a31b221d57 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Fri, 12 Nov 2021 18:04:34 -0500 Subject: [PATCH] Build fixes, ephemeral keying logic and ratchet logic, split out symmetric key since it is a non-trivial type. --- zerotier-core-crypto/src/hash.rs | 12 +- zerotier-core-crypto/src/sidhp751/sidh.rs | 22 +- zerotier-network-hypervisor/src/lib.rs | 4 +- .../src/vl1/ephemeral.rs | 213 ++++++++++++++++-- .../src/vl1/fragmentedpacket.rs | 2 +- zerotier-network-hypervisor/src/vl1/mod.rs | 2 +- zerotier-network-hypervisor/src/vl1/path.rs | 3 +- .../src/vl1/protocol.rs | 34 ++- .../src/vl1/symmetricsecret.rs | 11 +- .../src/vl1/whoisqueue.rs | 3 +- zerotier-network-hypervisor/src/vl2/switch.rs | 2 +- 11 files changed, 267 insertions(+), 41 deletions(-) diff --git a/zerotier-core-crypto/src/hash.rs b/zerotier-core-crypto/src/hash.rs index bfa25e363..bc3ba77ee 100644 --- a/zerotier-core-crypto/src/hash.rs +++ b/zerotier-core-crypto/src/hash.rs @@ -25,10 +25,10 @@ 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(); - let _ = m.set_key(key); - let _ = m.update(msg); + 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]; - let _ = m.get_mac(&mut h); + m.get_mac(&mut h).expect("FATAL: HMAC-SHA512 failed"); h } @@ -77,10 +77,10 @@ impl SHA384 { pub fn hmac(key: &[u8], msg: &[u8]) -> [u8; SHA384_HASH_SIZE] { let mut m = gcrypt::mac::Mac::new(gcrypt::mac::Algorithm::HmacSha384).unwrap(); - let _ = m.set_key(key); - let _ = m.update(msg); + m.set_key(key).expect("FATAL: invalid HMAC-SHA384 key"); + m.update(msg).expect("FATAL: HMAC-SHA384 failed"); let mut h = [0_u8; SHA384_HASH_SIZE]; - let _ = m.get_mac(&mut h); + m.get_mac(&mut h).expect("FATAL: HMAC-SHA384 failed"); h } diff --git a/zerotier-core-crypto/src/sidhp751/sidh.rs b/zerotier-core-crypto/src/sidhp751/sidh.rs index 3010040ae..9457ce950 100644 --- a/zerotier-core-crypto/src/sidhp751/sidh.rs +++ b/zerotier-core-crypto/src/sidhp751/sidh.rs @@ -25,11 +25,11 @@ use rand_core::{RngCore, CryptoRng}; use quickcheck::{Arbitrary, Gen, QuickCheck}; /// The secret key size, in bytes. -pub const SECRET_KEY_SIZE: usize = 48; +pub const SIDH_P751_SECRET_KEY_SIZE: usize = 48; /// The public key size, in bytes. -pub const PUBLIC_KEY_SIZE: usize = 564; +pub const SIDH_P751_PUBLIC_KEY_SIZE: usize = 564; /// The shared secret size, in bytes. -pub const SHARED_SECRET_SIZE: usize = 188; +pub const SIDH_P751_SHARED_SECRET_SIZE: usize = 188; const MAX_INT_POINTS_ALICE: usize = 8; const MAX_INT_POINTS_BOB: usize = 10; @@ -125,7 +125,7 @@ impl SIDHPublicKeyBob { /// Alice's secret key. #[derive(Copy, Clone)] pub struct SIDHSecretKeyAlice { - pub scalar: [u8; SECRET_KEY_SIZE], + pub scalar: [u8; SIDH_P751_SECRET_KEY_SIZE], } impl Debug for SIDHSecretKeyAlice { @@ -206,7 +206,7 @@ impl SIDHSecretKeyAlice { /// Compute (Alice's view of) a shared secret using Alice's secret key and Bob's public key. #[allow(non_snake_case)] - pub fn shared_secret(&self, bob_public: &SIDHPublicKeyBob) -> [u8; SHARED_SECRET_SIZE] { + pub fn shared_secret(&self, bob_public: &SIDHPublicKeyBob) -> [u8; SIDH_P751_SHARED_SECRET_SIZE] { let current_curve = ProjectiveCurveParameters::recover_curve_parameters(&bob_public.affine_xP, &bob_public.affine_xQ, &bob_public.affine_xQmP); let xP = ProjectivePoint::from_affine(&bob_public.affine_xP); let xQ = ProjectivePoint::from_affine(&bob_public.affine_xQ); @@ -250,7 +250,7 @@ impl SIDHSecretKeyAlice { /// Bob's secret key. #[derive(Copy, Clone)] pub struct SIDHSecretKeyBob { - pub scalar: [u8; SECRET_KEY_SIZE], + pub scalar: [u8; SIDH_P751_SECRET_KEY_SIZE], } impl Debug for SIDHSecretKeyBob { @@ -325,7 +325,7 @@ impl SIDHSecretKeyBob { /// Compute (Bob's view of) a shared secret using Bob's secret key and Alice's public key. #[allow(non_snake_case)] - pub fn shared_secret(&self, alice_public: &SIDHPublicKeyAlice) -> [u8; SHARED_SECRET_SIZE] { + pub fn shared_secret(&self, alice_public: &SIDHPublicKeyAlice) -> [u8; SIDH_P751_SHARED_SECRET_SIZE] { let mut current_curve = ProjectiveCurveParameters::recover_curve_parameters(&alice_public.affine_xP, &alice_public.affine_xQ, &alice_public.affine_xQmP); let xP = ProjectivePoint::from_affine(&alice_public.affine_xP); let xQ = ProjectivePoint::from_affine(&alice_public.affine_xQ); @@ -367,7 +367,7 @@ impl SIDHSecretKeyBob { /// implement SIDH validation, each keypair should be used for at most one /// shared secret computation. pub fn generate_alice_keypair(rng: &mut R) -> (SIDHPublicKeyAlice, SIDHSecretKeyAlice) { - let mut scalar = [0u8; SECRET_KEY_SIZE]; + let mut scalar = [0u8; SIDH_P751_SECRET_KEY_SIZE]; rng.fill_bytes(&mut scalar[..]); // Bit-twiddle to ensure scalar is in 2*[0,2^371): @@ -388,7 +388,7 @@ pub fn generate_alice_keypair(rng: &mut R) -> (SIDHPubli /// implement SIDH validation, each keypair should be used for at most one /// shared secret computation. pub fn generate_bob_keypair(rng: &mut R) -> (SIDHPublicKeyBob, SIDHSecretKeyBob) { - let mut scalar = [0u8; SECRET_KEY_SIZE]; + let mut scalar = [0u8; SIDH_P751_SECRET_KEY_SIZE]; // Perform rejection sampling to obtain a random value in [0,3^238]: let mut ok: u32 = 1; for _ in 0..102 { @@ -501,7 +501,7 @@ mod test { // // This function just exists to ensure that the fast isogeny-tree strategy works correctly. #[allow(non_snake_case)] - pub fn alice_shared_secret_slow(bob_public: &SIDHPublicKeyBob, alice_secret: &SIDHSecretKeyAlice) -> [u8; SHARED_SECRET_SIZE] { + pub fn alice_shared_secret_slow(bob_public: &SIDHPublicKeyBob, alice_secret: &SIDHSecretKeyAlice) -> [u8; SIDH_P751_SHARED_SECRET_SIZE] { let current_curve = ProjectiveCurveParameters::recover_curve_parameters(&bob_public.affine_xP, &bob_public.affine_xQ, &bob_public.affine_xQmP); let xP = ProjectivePoint::from_affine(&bob_public.affine_xP); let xQ = ProjectivePoint::from_affine(&bob_public.affine_xQ); @@ -532,7 +532,7 @@ mod test { // // This function just exists to ensure that the fast isogeny-tree strategy works correctly. #[allow(non_snake_case)] - pub fn bob_shared_secret_slow(alice_public: &SIDHPublicKeyAlice, bob_secret: &SIDHSecretKeyBob) -> [u8; SHARED_SECRET_SIZE] { + pub fn bob_shared_secret_slow(alice_public: &SIDHPublicKeyAlice, bob_secret: &SIDHSecretKeyBob) -> [u8; SIDH_P751_SHARED_SECRET_SIZE] { let mut current_curve = ProjectiveCurveParameters::recover_curve_parameters(&alice_public.affine_xP, &alice_public.affine_xQ, &alice_public.affine_xQmP); let xP = ProjectivePoint::from_affine(&alice_public.affine_xP); let xQ = ProjectivePoint::from_affine(&alice_public.affine_xQ); diff --git a/zerotier-network-hypervisor/src/lib.rs b/zerotier-network-hypervisor/src/lib.rs index c8d6be57f..992a35bc7 100644 --- a/zerotier-network-hypervisor/src/lib.rs +++ b/zerotier-network-hypervisor/src/lib.rs @@ -15,13 +15,13 @@ pub mod defaults; mod node; /// Standard packet buffer type including pool container. -pub type PacketBuffer = crate::util::pool::Pooled, crate::vl1::buffer::PooledBufferFactory<{ crate::vl1::protocol::PACKET_SIZE_MAX }>>; +pub type PacketBuffer = crate::util::pool::Pooled, crate::PacketBufferFactory>; /// Factory type to supply to a new PacketBufferPool. pub type PacketBufferFactory = crate::vl1::buffer::PooledBufferFactory<{ crate::vl1::protocol::PACKET_SIZE_MAX }>; /// Source for instances of PacketBuffer -pub type PacketBufferPool = crate::util::pool::Pool, crate::vl1::buffer::PacketBufferFactory>; +pub type PacketBufferPool = crate::util::pool::Pool, crate::PacketBufferFactory>; pub use node::{CallerInterface, Node}; diff --git a/zerotier-network-hypervisor/src/vl1/ephemeral.rs b/zerotier-network-hypervisor/src/vl1/ephemeral.rs index 485045fce..caddde44a 100644 --- a/zerotier-network-hypervisor/src/vl1/ephemeral.rs +++ b/zerotier-network-hypervisor/src/vl1/ephemeral.rs @@ -7,22 +7,21 @@ */ use std::sync::atomic::AtomicU32; +use std::io::Write; +use std::convert::TryInto; -use zerotier_core_crypto::c25519::C25519KeyPair; -use zerotier_core_crypto::hash::SHA384_HASH_SIZE; -use zerotier_core_crypto::p521::P521KeyPair; +use zerotier_core_crypto::c25519::{C25519KeyPair, C25519_PUBLIC_KEY_SIZE}; +use zerotier_core_crypto::hash::{SHA384_HASH_SIZE, SHA384}; +use zerotier_core_crypto::p521::{P521KeyPair, P521_PUBLIC_KEY_SIZE, P521PublicKey}; use zerotier_core_crypto::random::SecureRandom; use zerotier_core_crypto::secret::Secret; -use zerotier_core_crypto::sidhp751::{SIDHPublicKeyAlice, SIDHPublicKeyBob, SIDHSecretKeyAlice, SIDHSecretKeyBob}; +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::EPHEMERAL_SECRET_SIDH_TTL; +use crate::vl1::protocol::EphemeralKeyAgreementAlgorithm; use crate::vl1::symmetricsecret::SymmetricSecret; -const EPHEMERAL_CIPHER_C25519: u8 = 1; -const EPHEMERAL_CIPHER_P521: u8 = 2; -const EPHEMERAL_CIPHER_SIDH_P751: u8 = 3; - #[derive(Copy, Clone)] enum SIDHSecretKey { Alice(SIDHPublicKeyAlice, SIDHSecretKeyAlice), @@ -43,13 +42,22 @@ impl SIDHSecretKey { pub fn generate(local_address: Address, remote_address: Address) -> SIDHSecretKey { let mut rng = SecureRandom::get(); if local_address < remote_address { - let (p, s) = zerotier_core_crypto::sidh::sidh::generate_alice_keypair(&mut rng); + let (p, s) = zerotier_core_crypto::sidhp751::generate_alice_keypair(&mut rng); SIDHSecretKey::Alice(p, s) } else { - let (p, s) = zerotier_core_crypto::sidh::sidh::generate_bob_keypair(&mut rng); + let (p, s) = zerotier_core_crypto::sidhp751::generate_bob_keypair(&mut rng); SIDHSecretKey::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, + } + } } /// An ephemeral secret key negotiated to implement forward secrecy. @@ -57,7 +65,7 @@ pub struct EphemeralSecret { timestamp_ticks: i64, c25519: C25519KeyPair, p521: P521KeyPair, - sidhp751: SIDHSecretKey, + sidhp751: Option, } impl EphemeralSecret { @@ -70,19 +78,194 @@ impl EphemeralSecret { timestamp_ticks: time_ticks, c25519: C25519KeyPair::generate(true), p521: P521KeyPair::generate(true).expect("NIST P-521 key pair generation failed"), - sidhp751: SIDHSecretKey::generate(local_address, remote_address), + sidhp751: Some(SIDHSecretKey::generate(local_address, remote_address)), } } /// Create a public version of this ephemeral secret to share with our counterparty. - pub fn public(&self) -> Vec { - todo!() + 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); + + b.push(EphemeralKeyAgreementAlgorithm::C25519 as u8); + let _ = varint::write(&mut b, C25519_PUBLIC_KEY_SIZE as u64); + let _ = b.write_all(&self.c25519.public_bytes()); + + let _ = self.sidhp751.map(|sidhp751| { + b.push(EphemeralKeyAgreementAlgorithm::SIDHP751 as u8); + let _ = varint::write(&mut b, (SIDH_P751_PUBLIC_KEY_SIZE + 1) as u64); + b.push(sidhp751.role()); + let pk = match &sidhp751 { + SIDHSecretKey::Alice(a, _) => a.to_bytes(), + SIDHSecretKey::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, FIPS-compliant algorithm secret). Order has no actual + // implication for security. + + b.push(EphemeralKeyAgreementAlgorithm::NistP521ECDH as u8); + let _ = varint::write(&mut b, P521_PUBLIC_KEY_SIZE as u64); + let _ = b.write_all(self.p521.public_key_bytes()); + + b + } + + /// Perform ephemeral key agreement. + /// + /// None is returned if the public key data is malformed, no algorithms overlap, etc. + /// + /// Input is the previous session key. The long-lived identity key exchange key starts + /// the ratchet sequence, or rather a key derived from it for this purpose. + /// + /// 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 (ratchet_count, mut key) = previous_ephemeral_secret.map_or_else(|| { + (0_u64, static_secret.next_ephemeral_ratchet_key.clone()) + }, |previous_ephemeral_secret| { + (previous_ephemeral_secret.ratchet_count + 1, Secret(SHA384::hmac(&static_secret.next_ephemeral_ratchet_key.0, &previous_ephemeral_secret.secret.next_ephemeral_ratchet_key.0))) + }); + + let mut algs: Vec = Vec::with_capacity(3); + let mut other_public_bytes = other_public_bytes; + while !other_public_bytes.is_empty() { + let cipher = 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; + + match cipher.try_into() { + + Ok(EphemeralKeyAgreementAlgorithm::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 = SHA384::hmac(&key.0, &c25519_secret.0); + algs.push(EphemeralKeyAgreementAlgorithm::C25519); + }, + + Ok(EphemeralKeyAgreementAlgorithm::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(SIDHSecretKey::Alice(_, seck)) => { + if other_public_bytes[0] != 0 { // Alice can't agree with Alice + None + } else { + Some(Secret(seck.shared_secret(&SIDHPublicKeyBob::from_bytes(&other_public_bytes[1..(SIDH_P751_PUBLIC_KEY_SIZE + 1)])))) + } + }, + Some(SIDHSecretKey::Bob(_, seck)) => { + if other_public_bytes[0] != 1 { // Bob can't agree with Bob + None + } else { + Some(Secret(seck.shared_secret(&SIDHPublicKeyAlice::from_bytes(&other_public_bytes[1..(SIDH_P751_PUBLIC_KEY_SIZE + 1)])))) + } + }, + None => None, + }.map(|sidh_secret| { + key.0 = SHA384::hmac(&key.0, &sidh_secret.0); + algs.push(EphemeralKeyAgreementAlgorithm::SIDHP751); + }); + other_public_bytes = &other_public_bytes[(SIDH_P751_PUBLIC_KEY_SIZE + 1)..]; + }, + + Ok(EphemeralKeyAgreementAlgorithm::NistP521ECDH) => { + if other_public_bytes.len() < P521_PUBLIC_KEY_SIZE || key_len != P521_PUBLIC_KEY_SIZE { + return None; + } + let p521_public = P521PublicKey::from_bytes(&other_public_bytes[0..P521_PUBLIC_KEY_SIZE]); + other_public_bytes = &other_public_bytes[P521_PUBLIC_KEY_SIZE..]; + if p521_public.is_none() { + return None; + } + let p521_key = self.p521.agree(p521_public.as_ref().unwrap()); + if p521_key.is_none() { + return None; + } + key.0 = SHA384::hmac(&key.0, &p521_key.unwrap().0); + algs.push(EphemeralKeyAgreementAlgorithm::NistP521ECDH); + }, + + Err(_) => { + if other_public_bytes.len() < key_len { + return None; + } + other_public_bytes = &other_public_bytes[key_len..]; + } + + } + } + + return if !algs.is_empty() { + Some(EphemeralSymmetricSecret { + secret: SymmetricSecret::new(key), + agreement_algorithms: algs, + agreement_timestamp_ticks: time_ticks, + local_secret_timestamp_ticks: self.timestamp_ticks, + ratchet_count, + encrypt_uses: AtomicU32::new(0), + decrypt_uses: AtomicU32::new(0) + }) + } else { + None + }; } } pub struct EphemeralSymmetricSecret { secret: SymmetricSecret, + agreement_algorithms: Vec, agreement_timestamp_ticks: i64, local_secret_timestamp_ticks: i64, + ratchet_count: u64, encrypt_uses: AtomicU32, + decrypt_uses: AtomicU32, +} + +impl EphemeralSymmetricSecret { + #[inline(always)] + pub fn use_secret_to_encrypt(&self) -> &SymmetricSecret { + let _ = self.encrypt_uses.fetch_add(1, std::sync::atomic::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); + &self.secret + } + + pub fn is_fips_compliant(&self) -> bool { + self.agreement_algorithms.last().map_or(false, |alg| alg.is_fips_compliant()) + } +} + +#[cfg(test)] +mod tests { + use crate::vl1::ephemeral::EphemeralSecret; + use crate::vl1::Address; + use crate::vl1::symmetricsecret::SymmetricSecret; + use zerotier_core_crypto::secret::Secret; + + #[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()); + let bob = EphemeralSecret::new(1, Address::from_u64(0xbeefdead00).unwrap(), Address::from_u64(0xdeadbeef00).unwrap()); + 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(); + let bob_key = bob.agree(2, &static_secret, None, alice_public_bytes.as_slice()).unwrap(); + assert_eq!(&alice_key.secret.key.0, &bob_key.secret.key.0); + //println!("ephemeral_agreement secret: {}", zerotier_core_crypto::hex::to_string(&alice_key.secret.key.0)); + } } diff --git a/zerotier-network-hypervisor/src/vl1/fragmentedpacket.rs b/zerotier-network-hypervisor/src/vl1/fragmentedpacket.rs index 5e1d58f1e..fdf3d2e98 100644 --- a/zerotier-network-hypervisor/src/vl1/fragmentedpacket.rs +++ b/zerotier-network-hypervisor/src/vl1/fragmentedpacket.rs @@ -6,7 +6,7 @@ * https://www.zerotier.com/ */ -use crate::vl1::vl1node::PacketBuffer; +use crate::PacketBuffer; use crate::vl1::protocol::*; /// Packet fragment re-assembler and container. diff --git a/zerotier-network-hypervisor/src/vl1/mod.rs b/zerotier-network-hypervisor/src/vl1/mod.rs index c45d51733..245c352b6 100644 --- a/zerotier-network-hypervisor/src/vl1/mod.rs +++ b/zerotier-network-hypervisor/src/vl1/mod.rs @@ -33,4 +33,4 @@ pub use dictionary::Dictionary; pub use inetaddress::InetAddress; pub use peer::Peer; pub use path::Path; -pub use vl1node::{PacketBuffer, PacketBufferPool, PacketBufferFactory, VL1CallerInterface}; +pub use vl1node::{VL1Node, VL1CallerInterface}; diff --git a/zerotier-network-hypervisor/src/vl1/path.rs b/zerotier-network-hypervisor/src/vl1/path.rs index 3b26aac8e..3f64cb2eb 100644 --- a/zerotier-network-hypervisor/src/vl1/path.rs +++ b/zerotier-network-hypervisor/src/vl1/path.rs @@ -14,8 +14,9 @@ use parking_lot::Mutex; use crate::util::U64PassThroughHasher; use crate::vl1::Endpoint; use crate::vl1::fragmentedpacket::FragmentedPacket; -use crate::vl1::vl1node::{PacketBuffer, VL1CallerInterface}; +use crate::vl1::vl1node::VL1CallerInterface; use crate::vl1::protocol::*; +use crate::PacketBuffer; /// Keepalive interval for paths in milliseconds. pub(crate) const PATH_KEEPALIVE_INTERVAL: i64 = 20000; diff --git a/zerotier-network-hypervisor/src/vl1/protocol.rs b/zerotier-network-hypervisor/src/vl1/protocol.rs index 46aeddf6d..a6441009d 100644 --- a/zerotier-network-hypervisor/src/vl1/protocol.rs +++ b/zerotier-network-hypervisor/src/vl1/protocol.rs @@ -10,6 +10,7 @@ use std::mem::MaybeUninit; use crate::vl1::Address; use crate::vl1::buffer::{RawObject, Buffer}; +use std::convert::TryFrom; pub const VERB_VL1_NOP: u8 = 0x00; pub const VERB_VL1_HELLO: u8 = 0x01; @@ -47,7 +48,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 acknowledgement of a shared secret. -pub const KBKDF_KEY_USAGE_LABEL_EPHEMERAL_ACK: u8 = b'A'; +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 @@ -176,6 +177,37 @@ pub const WHOIS_RETRY_MAX: u16 = 3; /// Maximum number of packets to queue up behind a WHOIS. pub const WHOIS_MAX_WAITING_PACKETS: usize = 64; +#[derive(Clone, Copy)] +#[repr(u8)] +pub enum EphemeralKeyAgreementAlgorithm { + C25519 = 1, + SIDHP751 = 2, + NistP521ECDH = 3 +} + +impl EphemeralKeyAgreementAlgorithm { + pub fn is_fips_compliant(&self) -> bool { + match self { + Self::NistP521ECDH => true, + _ => false + } + } +} + +impl TryFrom for EphemeralKeyAgreementAlgorithm { + type Error = (); + + #[inline(always)] + fn try_from(value: u8) -> Result { + match value { + 1 => Ok(Self::C25519), + 2 => Ok(Self::SIDHP751), + 3 => Ok(Self::NistP521ECDH), + _ => Err(()) + } + } +} + /// Compress a packet and return true if compressed. /// The 'dest' buffer must be empty (will panic otherwise). A return value of false indicates an error or /// that the data was not compressible. The state of the destination buffer is undefined on a return diff --git a/zerotier-network-hypervisor/src/vl1/symmetricsecret.rs b/zerotier-network-hypervisor/src/vl1/symmetricsecret.rs index 3018c745a..58c6f9f7a 100644 --- a/zerotier-network-hypervisor/src/vl1/symmetricsecret.rs +++ b/zerotier-network-hypervisor/src/vl1/symmetricsecret.rs @@ -14,7 +14,7 @@ use zerotier_core_crypto::kbkdf::zt_kbkdf_hmac_sha384; use zerotier_core_crypto::secret::Secret; use crate::util::pool::{Pool, PoolFactory}; -use crate::vl1::protocol::{KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K0, KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K1, KBKDF_KEY_USAGE_LABEL_HELLO_DICTIONARY_ENCRYPT, KBKDF_KEY_USAGE_LABEL_PACKET_HMAC}; +use crate::vl1::protocol::{KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K0, KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K1, KBKDF_KEY_USAGE_LABEL_HELLO_DICTIONARY_ENCRYPT, KBKDF_KEY_USAGE_LABEL_PACKET_HMAC, KBKDF_KEY_USAGE_LABEL_EPHEMERAL_RATCHET}; pub struct AesGmacSivPoolFactory(Secret, Secret); @@ -32,13 +32,21 @@ impl PoolFactory for AesGmacSivPoolFactory { pub struct SymmetricSecret { pub key: Secret, pub packet_hmac_key: Secret, + pub next_ephemeral_ratchet_key: Secret, pub hello_dictionary_keyed_cipher: Mutex, pub aes_gmac_siv: Pool, } +impl PartialEq for SymmetricSecret { + fn eq(&self, other: &Self) -> bool { self.key.0.eq(&other.key.0) } +} + +impl Eq for SymmetricSecret {} + impl SymmetricSecret { pub fn new(base_key: Secret) -> SymmetricSecret { let usage_packet_hmac = zt_kbkdf_hmac_sha384(&base_key.0, KBKDF_KEY_USAGE_LABEL_PACKET_HMAC, 0, 0); + let usage_ephemeral_ratchet = zt_kbkdf_hmac_sha384(&base_key.0, KBKDF_KEY_USAGE_LABEL_EPHEMERAL_RATCHET, 0, 0); let usage_hello_dictionary_key = zt_kbkdf_hmac_sha384(&base_key.0, KBKDF_KEY_USAGE_LABEL_HELLO_DICTIONARY_ENCRYPT, 0, 0); let aes_factory = AesGmacSivPoolFactory( zt_kbkdf_hmac_sha384(&base_key.0, KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K0, 0, 0), @@ -46,6 +54,7 @@ impl SymmetricSecret { SymmetricSecret { key: base_key, packet_hmac_key: usage_packet_hmac, + next_ephemeral_ratchet_key: usage_ephemeral_ratchet, hello_dictionary_keyed_cipher: Mutex::new(AesCtr::new(&usage_hello_dictionary_key.0[0..32])), aes_gmac_siv: Pool::new(2, aes_factory), } diff --git a/zerotier-network-hypervisor/src/vl1/whoisqueue.rs b/zerotier-network-hypervisor/src/vl1/whoisqueue.rs index db4c361f4..c80cd33d7 100644 --- a/zerotier-network-hypervisor/src/vl1/whoisqueue.rs +++ b/zerotier-network-hypervisor/src/vl1/whoisqueue.rs @@ -13,8 +13,9 @@ use parking_lot::Mutex; use crate::util::gate::IntervalGate; use crate::vl1::Address; use crate::vl1::fragmentedpacket::FragmentedPacket; -use crate::vl1::vl1node::{VL1Node, PacketBuffer, VL1CallerInterface}; +use crate::vl1::vl1node::{VL1Node, VL1CallerInterface}; use crate::vl1::protocol::{WHOIS_RETRY_INTERVAL, WHOIS_MAX_WAITING_PACKETS, WHOIS_RETRY_MAX}; +use crate::PacketBuffer; pub(crate) enum QueuedPacket { Unfragmented(PacketBuffer), diff --git a/zerotier-network-hypervisor/src/vl2/switch.rs b/zerotier-network-hypervisor/src/vl2/switch.rs index 87ad9b0c1..91bc4566b 100644 --- a/zerotier-network-hypervisor/src/vl2/switch.rs +++ b/zerotier-network-hypervisor/src/vl2/switch.rs @@ -32,6 +32,6 @@ impl VL1PacketHandler for Switch { impl Switch { pub fn new() -> Self { - Self + Self{} } }