mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-06-05 20:13:44 +02:00
Build fixes, ephemeral keying logic and ratchet logic, split out symmetric key since it is a non-trivial type.
This commit is contained in:
parent
184c4aede1
commit
06bd77946b
11 changed files with 267 additions and 41 deletions
|
@ -25,10 +25,10 @@ impl SHA512 {
|
||||||
|
|
||||||
pub fn hmac(key: &[u8], msg: &[u8]) -> [u8; SHA512_HASH_SIZE] {
|
pub fn hmac(key: &[u8], msg: &[u8]) -> [u8; SHA512_HASH_SIZE] {
|
||||||
let mut m = gcrypt::mac::Mac::new(gcrypt::mac::Algorithm::HmacSha512).unwrap();
|
let mut m = gcrypt::mac::Mac::new(gcrypt::mac::Algorithm::HmacSha512).unwrap();
|
||||||
let _ = m.set_key(key);
|
m.set_key(key).expect("FATAL: invalid HMAC-SHA512 key");;
|
||||||
let _ = m.update(msg);
|
m.update(msg).expect("FATAL: HMAC-SHA512 failed");
|
||||||
let mut h = [0_u8; SHA512_HASH_SIZE];
|
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
|
h
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,10 +77,10 @@ impl SHA384 {
|
||||||
|
|
||||||
pub fn hmac(key: &[u8], msg: &[u8]) -> [u8; SHA384_HASH_SIZE] {
|
pub fn hmac(key: &[u8], msg: &[u8]) -> [u8; SHA384_HASH_SIZE] {
|
||||||
let mut m = gcrypt::mac::Mac::new(gcrypt::mac::Algorithm::HmacSha384).unwrap();
|
let mut m = gcrypt::mac::Mac::new(gcrypt::mac::Algorithm::HmacSha384).unwrap();
|
||||||
let _ = m.set_key(key);
|
m.set_key(key).expect("FATAL: invalid HMAC-SHA384 key");
|
||||||
let _ = m.update(msg);
|
m.update(msg).expect("FATAL: HMAC-SHA384 failed");
|
||||||
let mut h = [0_u8; SHA384_HASH_SIZE];
|
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
|
h
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,11 +25,11 @@ use rand_core::{RngCore, CryptoRng};
|
||||||
use quickcheck::{Arbitrary, Gen, QuickCheck};
|
use quickcheck::{Arbitrary, Gen, QuickCheck};
|
||||||
|
|
||||||
/// The secret key size, in bytes.
|
/// 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.
|
/// 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.
|
/// 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_ALICE: usize = 8;
|
||||||
const MAX_INT_POINTS_BOB: usize = 10;
|
const MAX_INT_POINTS_BOB: usize = 10;
|
||||||
|
@ -125,7 +125,7 @@ impl SIDHPublicKeyBob {
|
||||||
/// Alice's secret key.
|
/// Alice's secret key.
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct SIDHSecretKeyAlice {
|
pub struct SIDHSecretKeyAlice {
|
||||||
pub scalar: [u8; SECRET_KEY_SIZE],
|
pub scalar: [u8; SIDH_P751_SECRET_KEY_SIZE],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for SIDHSecretKeyAlice {
|
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.
|
/// Compute (Alice's view of) a shared secret using Alice's secret key and Bob's public key.
|
||||||
#[allow(non_snake_case)]
|
#[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 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 xP = ProjectivePoint::from_affine(&bob_public.affine_xP);
|
||||||
let xQ = ProjectivePoint::from_affine(&bob_public.affine_xQ);
|
let xQ = ProjectivePoint::from_affine(&bob_public.affine_xQ);
|
||||||
|
@ -250,7 +250,7 @@ impl SIDHSecretKeyAlice {
|
||||||
/// Bob's secret key.
|
/// Bob's secret key.
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct SIDHSecretKeyBob {
|
pub struct SIDHSecretKeyBob {
|
||||||
pub scalar: [u8; SECRET_KEY_SIZE],
|
pub scalar: [u8; SIDH_P751_SECRET_KEY_SIZE],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for SIDHSecretKeyBob {
|
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.
|
/// Compute (Bob's view of) a shared secret using Bob's secret key and Alice's public key.
|
||||||
#[allow(non_snake_case)]
|
#[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 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 xP = ProjectivePoint::from_affine(&alice_public.affine_xP);
|
||||||
let xQ = ProjectivePoint::from_affine(&alice_public.affine_xQ);
|
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
|
/// implement SIDH validation, each keypair should be used for at most one
|
||||||
/// shared secret computation.
|
/// shared secret computation.
|
||||||
pub fn generate_alice_keypair<R: RngCore + CryptoRng>(rng: &mut R) -> (SIDHPublicKeyAlice, SIDHSecretKeyAlice) {
|
pub fn generate_alice_keypair<R: RngCore + CryptoRng>(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[..]);
|
rng.fill_bytes(&mut scalar[..]);
|
||||||
|
|
||||||
// Bit-twiddle to ensure scalar is in 2*[0,2^371):
|
// Bit-twiddle to ensure scalar is in 2*[0,2^371):
|
||||||
|
@ -388,7 +388,7 @@ pub fn generate_alice_keypair<R: RngCore + CryptoRng>(rng: &mut R) -> (SIDHPubli
|
||||||
/// implement SIDH validation, each keypair should be used for at most one
|
/// implement SIDH validation, each keypair should be used for at most one
|
||||||
/// shared secret computation.
|
/// shared secret computation.
|
||||||
pub fn generate_bob_keypair<R: RngCore + CryptoRng>(rng: &mut R) -> (SIDHPublicKeyBob, SIDHSecretKeyBob) {
|
pub fn generate_bob_keypair<R: RngCore + CryptoRng>(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]:
|
// Perform rejection sampling to obtain a random value in [0,3^238]:
|
||||||
let mut ok: u32 = 1;
|
let mut ok: u32 = 1;
|
||||||
for _ in 0..102 {
|
for _ in 0..102 {
|
||||||
|
@ -501,7 +501,7 @@ mod test {
|
||||||
//
|
//
|
||||||
// This function just exists to ensure that the fast isogeny-tree strategy works correctly.
|
// This function just exists to ensure that the fast isogeny-tree strategy works correctly.
|
||||||
#[allow(non_snake_case)]
|
#[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 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 xP = ProjectivePoint::from_affine(&bob_public.affine_xP);
|
||||||
let xQ = ProjectivePoint::from_affine(&bob_public.affine_xQ);
|
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.
|
// This function just exists to ensure that the fast isogeny-tree strategy works correctly.
|
||||||
#[allow(non_snake_case)]
|
#[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 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 xP = ProjectivePoint::from_affine(&alice_public.affine_xP);
|
||||||
let xQ = ProjectivePoint::from_affine(&alice_public.affine_xQ);
|
let xQ = ProjectivePoint::from_affine(&alice_public.affine_xQ);
|
||||||
|
|
|
@ -15,13 +15,13 @@ pub mod defaults;
|
||||||
mod node;
|
mod node;
|
||||||
|
|
||||||
/// Standard packet buffer type including pool container.
|
/// Standard packet buffer type including pool container.
|
||||||
pub type PacketBuffer = crate::util::pool::Pooled<Buffer<{ crate::vl1::protocol::PACKET_SIZE_MAX }>, crate::vl1::buffer::PooledBufferFactory<{ crate::vl1::protocol::PACKET_SIZE_MAX }>>;
|
pub type PacketBuffer = crate::util::pool::Pooled<crate::vl1::buffer::Buffer<{ crate::vl1::protocol::PACKET_SIZE_MAX }>, crate::PacketBufferFactory>;
|
||||||
|
|
||||||
/// Factory type to supply to a new PacketBufferPool.
|
/// Factory type to supply to a new PacketBufferPool.
|
||||||
pub type PacketBufferFactory = crate::vl1::buffer::PooledBufferFactory<{ crate::vl1::protocol::PACKET_SIZE_MAX }>;
|
pub type PacketBufferFactory = crate::vl1::buffer::PooledBufferFactory<{ crate::vl1::protocol::PACKET_SIZE_MAX }>;
|
||||||
|
|
||||||
/// Source for instances of PacketBuffer
|
/// Source for instances of PacketBuffer
|
||||||
pub type PacketBufferPool = crate::util::pool::Pool<Buffer<{ crate::vl1::protocol::PACKET_SIZE_MAX }>, crate::vl1::buffer::PacketBufferFactory>;
|
pub type PacketBufferPool = crate::util::pool::Pool<crate::vl1::buffer::Buffer<{ crate::vl1::protocol::PACKET_SIZE_MAX }>, crate::PacketBufferFactory>;
|
||||||
|
|
||||||
pub use node::{CallerInterface, Node};
|
pub use node::{CallerInterface, Node};
|
||||||
|
|
||||||
|
|
|
@ -7,22 +7,21 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::sync::atomic::AtomicU32;
|
use std::sync::atomic::AtomicU32;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
|
||||||
use zerotier_core_crypto::c25519::C25519KeyPair;
|
use zerotier_core_crypto::c25519::{C25519KeyPair, C25519_PUBLIC_KEY_SIZE};
|
||||||
use zerotier_core_crypto::hash::SHA384_HASH_SIZE;
|
use zerotier_core_crypto::hash::{SHA384_HASH_SIZE, SHA384};
|
||||||
use zerotier_core_crypto::p521::P521KeyPair;
|
use zerotier_core_crypto::p521::{P521KeyPair, P521_PUBLIC_KEY_SIZE, P521PublicKey};
|
||||||
use zerotier_core_crypto::random::SecureRandom;
|
use zerotier_core_crypto::random::SecureRandom;
|
||||||
use zerotier_core_crypto::secret::Secret;
|
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::Address;
|
||||||
use crate::vl1::protocol::EPHEMERAL_SECRET_SIDH_TTL;
|
use crate::vl1::protocol::EphemeralKeyAgreementAlgorithm;
|
||||||
use crate::vl1::symmetricsecret::SymmetricSecret;
|
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)]
|
#[derive(Copy, Clone)]
|
||||||
enum SIDHSecretKey {
|
enum SIDHSecretKey {
|
||||||
Alice(SIDHPublicKeyAlice, SIDHSecretKeyAlice),
|
Alice(SIDHPublicKeyAlice, SIDHSecretKeyAlice),
|
||||||
|
@ -43,13 +42,22 @@ impl SIDHSecretKey {
|
||||||
pub fn generate(local_address: Address, remote_address: Address) -> SIDHSecretKey {
|
pub fn generate(local_address: Address, remote_address: Address) -> SIDHSecretKey {
|
||||||
let mut rng = SecureRandom::get();
|
let mut rng = SecureRandom::get();
|
||||||
if local_address < remote_address {
|
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)
|
SIDHSecretKey::Alice(p, s)
|
||||||
} else {
|
} 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)
|
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.
|
/// An ephemeral secret key negotiated to implement forward secrecy.
|
||||||
|
@ -57,7 +65,7 @@ pub struct EphemeralSecret {
|
||||||
timestamp_ticks: i64,
|
timestamp_ticks: i64,
|
||||||
c25519: C25519KeyPair,
|
c25519: C25519KeyPair,
|
||||||
p521: P521KeyPair,
|
p521: P521KeyPair,
|
||||||
sidhp751: SIDHSecretKey,
|
sidhp751: Option<SIDHSecretKey>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EphemeralSecret {
|
impl EphemeralSecret {
|
||||||
|
@ -70,19 +78,194 @@ impl EphemeralSecret {
|
||||||
timestamp_ticks: time_ticks,
|
timestamp_ticks: time_ticks,
|
||||||
c25519: C25519KeyPair::generate(true),
|
c25519: C25519KeyPair::generate(true),
|
||||||
p521: P521KeyPair::generate(true).expect("NIST P-521 key pair generation failed"),
|
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.
|
/// Create a public version of this ephemeral secret to share with our counterparty.
|
||||||
pub fn public(&self) -> Vec<u8> {
|
pub fn public_bytes(&self) -> Vec<u8> {
|
||||||
todo!()
|
let mut b: Vec<u8> = 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<EphemeralSymmetricSecret> {
|
||||||
|
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<EphemeralKeyAgreementAlgorithm> = 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 {
|
pub struct EphemeralSymmetricSecret {
|
||||||
secret: SymmetricSecret,
|
secret: SymmetricSecret,
|
||||||
|
agreement_algorithms: Vec<EphemeralKeyAgreementAlgorithm>,
|
||||||
agreement_timestamp_ticks: i64,
|
agreement_timestamp_ticks: i64,
|
||||||
local_secret_timestamp_ticks: i64,
|
local_secret_timestamp_ticks: i64,
|
||||||
|
ratchet_count: u64,
|
||||||
encrypt_uses: AtomicU32,
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* https://www.zerotier.com/
|
* https://www.zerotier.com/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use crate::vl1::vl1node::PacketBuffer;
|
use crate::PacketBuffer;
|
||||||
use crate::vl1::protocol::*;
|
use crate::vl1::protocol::*;
|
||||||
|
|
||||||
/// Packet fragment re-assembler and container.
|
/// Packet fragment re-assembler and container.
|
||||||
|
|
|
@ -33,4 +33,4 @@ pub use dictionary::Dictionary;
|
||||||
pub use inetaddress::InetAddress;
|
pub use inetaddress::InetAddress;
|
||||||
pub use peer::Peer;
|
pub use peer::Peer;
|
||||||
pub use path::Path;
|
pub use path::Path;
|
||||||
pub use vl1node::{PacketBuffer, PacketBufferPool, PacketBufferFactory, VL1CallerInterface};
|
pub use vl1node::{VL1Node, VL1CallerInterface};
|
||||||
|
|
|
@ -14,8 +14,9 @@ use parking_lot::Mutex;
|
||||||
use crate::util::U64PassThroughHasher;
|
use crate::util::U64PassThroughHasher;
|
||||||
use crate::vl1::Endpoint;
|
use crate::vl1::Endpoint;
|
||||||
use crate::vl1::fragmentedpacket::FragmentedPacket;
|
use crate::vl1::fragmentedpacket::FragmentedPacket;
|
||||||
use crate::vl1::vl1node::{PacketBuffer, VL1CallerInterface};
|
use crate::vl1::vl1node::VL1CallerInterface;
|
||||||
use crate::vl1::protocol::*;
|
use crate::vl1::protocol::*;
|
||||||
|
use crate::PacketBuffer;
|
||||||
|
|
||||||
/// Keepalive interval for paths in milliseconds.
|
/// Keepalive interval for paths in milliseconds.
|
||||||
pub(crate) const PATH_KEEPALIVE_INTERVAL: i64 = 20000;
|
pub(crate) const PATH_KEEPALIVE_INTERVAL: i64 = 20000;
|
||||||
|
|
|
@ -10,6 +10,7 @@ use std::mem::MaybeUninit;
|
||||||
|
|
||||||
use crate::vl1::Address;
|
use crate::vl1::Address;
|
||||||
use crate::vl1::buffer::{RawObject, Buffer};
|
use crate::vl1::buffer::{RawObject, Buffer};
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
pub const VERB_VL1_NOP: u8 = 0x00;
|
pub const VERB_VL1_NOP: u8 = 0x00;
|
||||||
pub const VERB_VL1_HELLO: u8 = 0x01;
|
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';
|
pub const KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K1: u8 = b'1';
|
||||||
|
|
||||||
/// KBKDF usage label for acknowledgement of a shared secret.
|
/// 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.
|
/// 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 = 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.
|
/// Maximum number of packets to queue up behind a WHOIS.
|
||||||
pub const WHOIS_MAX_WAITING_PACKETS: usize = 64;
|
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<u8> for EphemeralKeyAgreementAlgorithm {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
1 => Ok(Self::C25519),
|
||||||
|
2 => Ok(Self::SIDHP751),
|
||||||
|
3 => Ok(Self::NistP521ECDH),
|
||||||
|
_ => Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Compress a packet and return true if compressed.
|
/// 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
|
/// 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
|
/// that the data was not compressible. The state of the destination buffer is undefined on a return
|
||||||
|
|
|
@ -14,7 +14,7 @@ use zerotier_core_crypto::kbkdf::zt_kbkdf_hmac_sha384;
|
||||||
use zerotier_core_crypto::secret::Secret;
|
use zerotier_core_crypto::secret::Secret;
|
||||||
|
|
||||||
use crate::util::pool::{Pool, PoolFactory};
|
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<SHA384_HASH_SIZE>, Secret<SHA384_HASH_SIZE>);
|
pub struct AesGmacSivPoolFactory(Secret<SHA384_HASH_SIZE>, Secret<SHA384_HASH_SIZE>);
|
||||||
|
|
||||||
|
@ -32,13 +32,21 @@ impl PoolFactory<AesGmacSiv> for AesGmacSivPoolFactory {
|
||||||
pub struct SymmetricSecret {
|
pub struct SymmetricSecret {
|
||||||
pub key: Secret<SHA384_HASH_SIZE>,
|
pub key: Secret<SHA384_HASH_SIZE>,
|
||||||
pub packet_hmac_key: Secret<SHA384_HASH_SIZE>,
|
pub packet_hmac_key: Secret<SHA384_HASH_SIZE>,
|
||||||
|
pub next_ephemeral_ratchet_key: Secret<SHA384_HASH_SIZE>,
|
||||||
pub hello_dictionary_keyed_cipher: Mutex<AesCtr>,
|
pub hello_dictionary_keyed_cipher: Mutex<AesCtr>,
|
||||||
pub aes_gmac_siv: Pool<AesGmacSiv, AesGmacSivPoolFactory>,
|
pub aes_gmac_siv: Pool<AesGmacSiv, AesGmacSivPoolFactory>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialEq for SymmetricSecret {
|
||||||
|
fn eq(&self, other: &Self) -> bool { self.key.0.eq(&other.key.0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for SymmetricSecret {}
|
||||||
|
|
||||||
impl SymmetricSecret {
|
impl SymmetricSecret {
|
||||||
pub fn new(base_key: Secret<SHA384_HASH_SIZE>) -> SymmetricSecret {
|
pub fn new(base_key: Secret<SHA384_HASH_SIZE>) -> SymmetricSecret {
|
||||||
let usage_packet_hmac = zt_kbkdf_hmac_sha384(&base_key.0, KBKDF_KEY_USAGE_LABEL_PACKET_HMAC, 0, 0);
|
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 usage_hello_dictionary_key = zt_kbkdf_hmac_sha384(&base_key.0, KBKDF_KEY_USAGE_LABEL_HELLO_DICTIONARY_ENCRYPT, 0, 0);
|
||||||
let aes_factory = AesGmacSivPoolFactory(
|
let aes_factory = AesGmacSivPoolFactory(
|
||||||
zt_kbkdf_hmac_sha384(&base_key.0, KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K0, 0, 0),
|
zt_kbkdf_hmac_sha384(&base_key.0, KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K0, 0, 0),
|
||||||
|
@ -46,6 +54,7 @@ impl SymmetricSecret {
|
||||||
SymmetricSecret {
|
SymmetricSecret {
|
||||||
key: base_key,
|
key: base_key,
|
||||||
packet_hmac_key: usage_packet_hmac,
|
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])),
|
hello_dictionary_keyed_cipher: Mutex::new(AesCtr::new(&usage_hello_dictionary_key.0[0..32])),
|
||||||
aes_gmac_siv: Pool::new(2, aes_factory),
|
aes_gmac_siv: Pool::new(2, aes_factory),
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,9 @@ use parking_lot::Mutex;
|
||||||
use crate::util::gate::IntervalGate;
|
use crate::util::gate::IntervalGate;
|
||||||
use crate::vl1::Address;
|
use crate::vl1::Address;
|
||||||
use crate::vl1::fragmentedpacket::FragmentedPacket;
|
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::vl1::protocol::{WHOIS_RETRY_INTERVAL, WHOIS_MAX_WAITING_PACKETS, WHOIS_RETRY_MAX};
|
||||||
|
use crate::PacketBuffer;
|
||||||
|
|
||||||
pub(crate) enum QueuedPacket {
|
pub(crate) enum QueuedPacket {
|
||||||
Unfragmented(PacketBuffer),
|
Unfragmented(PacketBuffer),
|
||||||
|
|
|
@ -32,6 +32,6 @@ impl VL1PacketHandler for Switch {
|
||||||
|
|
||||||
impl Switch {
|
impl Switch {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self
|
Self{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue