mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-04-26 08:57:26 +02:00
Some cleanup and more testing for ephemeral ratchet.
This commit is contained in:
parent
5fb5c76694
commit
c39f38d818
5 changed files with 84 additions and 26 deletions
|
@ -62,14 +62,11 @@ pub const VERSION_STR: &'static str = "1.99.1";
|
|||
* 11 - 1.6.0 ... 2.0.0
|
||||
* + Supports AES-GMAC-SIV symmetric crypto, backported from v2 tree.
|
||||
* 20 - 2.0.0 ... CURRENT
|
||||
* + New more WAN-efficient P2P-assisted multicast algorithm
|
||||
* + HELLO and OK(HELLO) include an extra HMAC to harden authentication
|
||||
* + HELLO and OK(HELLO) carry meta-data in a dictionary that's encrypted
|
||||
* + Forward secrecy, key lifetime management
|
||||
* + Old planet/moon stuff is DEAD! Independent roots are easier.
|
||||
* + AES encryption with the SIV construction AES-GMAC-SIV
|
||||
* + New combined Curve25519/NIST P-384 identity type (type 1)
|
||||
* + Short probe packets to reduce probe bandwidth
|
||||
* + More aggressive NAT traversal techniques for IPv4 symmetric NATs
|
||||
* + New combined Curve25519/NIST P-384 identity
|
||||
*/
|
||||
pub const VERSION_PROTO: u8 = 20;
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
use std::cmp::Ordering;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
use zerotier_core_crypto::hash::SHA384_HASH_SIZE;
|
||||
|
||||
use crate::vl1::{Address, MAC};
|
||||
|
@ -26,18 +27,44 @@ pub const TYPE_HTTP: u8 = 8;
|
|||
pub const TYPE_WEBRTC: u8 = 9;
|
||||
pub const TYPE_ZEROTIER_ENCAP: u8 = 10;
|
||||
|
||||
/// A communication endpoint on the network where some ZeroTier node can be reached.
|
||||
///
|
||||
/// Currently only a few of these are supported. The rest are reserved for future use.
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub enum Endpoint {
|
||||
/// A null endpoint.
|
||||
Nil,
|
||||
|
||||
/// Via another node using unencapsulated relaying (e.g. via a root)
|
||||
/// Hash is a full hash of the identity for strong verification.
|
||||
ZeroTier(Address, [u8; SHA384_HASH_SIZE]),
|
||||
|
||||
/// Direct L2 Ethernet
|
||||
Ethernet(MAC),
|
||||
|
||||
/// Direct L2 Ethernet over WiFi-Direct (P2P WiFi)
|
||||
WifiDirect(MAC),
|
||||
|
||||
/// Local bluetooth
|
||||
Bluetooth(MAC),
|
||||
|
||||
/// Raw IP without a UDP or other header
|
||||
Ip(InetAddress),
|
||||
|
||||
/// Raw UDP, the default and usually preferred transport mode
|
||||
IpUdp(InetAddress),
|
||||
|
||||
/// Raw TCP with each packet prefixed by a varint size
|
||||
IpTcp(InetAddress),
|
||||
|
||||
/// HTTP streaming
|
||||
Http(String),
|
||||
|
||||
/// WebRTC data channel
|
||||
WebRTC(Vec<u8>),
|
||||
|
||||
/// Via another node using inner encapsulation via VERB_ENCAP.
|
||||
/// Hash is a full hash of the identity for strong verification.
|
||||
ZeroTierEncap(Address, [u8; SHA384_HASH_SIZE]),
|
||||
}
|
||||
|
||||
|
@ -47,6 +74,7 @@ impl Default for Endpoint {
|
|||
}
|
||||
|
||||
impl Endpoint {
|
||||
/// Get the IP address (and port if applicable) if this is an IP-based transport.
|
||||
#[inline(always)]
|
||||
pub fn ip(&self) -> Option<(&InetAddress, u8)> {
|
||||
match self {
|
||||
|
@ -73,6 +101,9 @@ impl Endpoint {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn is_nil(&self) -> bool { matches!(self, Endpoint::Nil) }
|
||||
|
||||
pub fn marshal<const BL: usize>(&self, buf: &mut Buffer<BL>) -> std::io::Result<()> {
|
||||
match self {
|
||||
Endpoint::Nil => {
|
||||
|
@ -239,6 +270,7 @@ impl Hash for Endpoint {
|
|||
|
||||
impl Ord for Endpoint {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
// Manually implement Ord to ensure that sort order is known and consistent.
|
||||
match (self, other) {
|
||||
(Endpoint::Nil, Endpoint::Nil) => Ordering::Equal,
|
||||
(Endpoint::ZeroTier(a, ah), Endpoint::ZeroTier(b, bh)) => a.cmp(b).then_with(|| ah.cmp(bh)),
|
||||
|
@ -274,7 +306,7 @@ impl ToString for Endpoint {
|
|||
Endpoint::IpTcp(ip) => format!("tcp:{}", ip.to_string()),
|
||||
Endpoint::Http(url) => url.clone(),
|
||||
Endpoint::WebRTC(offer) => format!("webrtc:{}", base64::encode_config(offer.as_slice(), base64::URL_SAFE_NO_PAD)),
|
||||
Endpoint::ZeroTierEncap(a, ah) => format!("zte:{}-{}", a.to_string(), base64::encode_config(ah, base64::URL_SAFE_NO_PAD)),
|
||||
Endpoint::ZeroTierEncap(a, ah) => format!("ztzt:{}-{}", a.to_string(), base64::encode_config(ah, base64::URL_SAFE_NO_PAD)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -118,14 +118,14 @@ impl EphemeralKeyPairSet {
|
|||
pub fn agree(self, time_ticks: i64, static_secret: &SymmetricSecret, previous_ephemeral_secret: Option<&EphemeralSymmetricSecret>, other_public_bytes: &[u8]) -> Option<EphemeralSymmetricSecret> {
|
||||
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(),
|
||||
static_secret.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)),
|
||||
previous_ephemeral_secret.secret.ephemeral_ratchet_key.clone(),
|
||||
previous_ephemeral_secret.c25519_ratchet_count,
|
||||
previous_ephemeral_secret.sidhp751_ratchet_count,
|
||||
previous_ephemeral_secret.nistp521_ratchet_count
|
||||
|
@ -167,8 +167,10 @@ impl EphemeralKeyPairSet {
|
|||
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);
|
||||
it_happened = true;
|
||||
fips_compliant_exchange = false;
|
||||
|
@ -179,19 +181,20 @@ impl EphemeralKeyPairSet {
|
|||
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
|
||||
None
|
||||
} else {
|
||||
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
|
||||
None
|
||||
} else {
|
||||
Some(Secret(seck.shared_secret(&SIDHPublicKeyAlice::from_bytes(&other_public_bytes[1..(SIDH_P751_PUBLIC_KEY_SIZE + 1)]))))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
None => None,
|
||||
|
@ -208,15 +211,18 @@ impl EphemeralKeyPairSet {
|
|||
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);
|
||||
it_happened = true;
|
||||
fips_compliant_exchange = true;
|
||||
|
@ -257,7 +263,7 @@ impl EphemeralKeyPairSet {
|
|||
pub(crate) struct EphemeralSymmetricSecret {
|
||||
/// Current ephemeral secret key.
|
||||
pub secret: SymmetricSecret,
|
||||
/// First 16 bytes of SHA384(current ephemeral secret).
|
||||
/// An identifier used to check negotiation of the ratchet.
|
||||
ratchet_state: [u8; 16],
|
||||
/// Time at or after which we should start trying to re-key.
|
||||
rekey_time: i64,
|
||||
|
@ -290,10 +296,12 @@ impl EphemeralSymmetricSecret {
|
|||
&self.secret
|
||||
}
|
||||
|
||||
#[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
|
||||
}
|
||||
|
@ -344,15 +352,38 @@ mod tests {
|
|||
use zerotier_core_crypto::secret::Secret;
|
||||
|
||||
#[test]
|
||||
fn ephemeral_agreement() {
|
||||
fn ratchet() {
|
||||
let static_secret = SymmetricSecret::new(Secret([1_u8; 48]));
|
||||
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();
|
||||
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));
|
||||
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, None);
|
||||
let mut bob = EphemeralKeyPairSet::new(bob_address, alice_address, None);
|
||||
let mut prev_alice_key = None;
|
||||
let mut prev_bob_key = None;
|
||||
let ratchets = 16;
|
||||
for t in 1..ratchets+1 {
|
||||
let alice_public = alice.public_bytes();
|
||||
let bob_public = bob.public_bytes();
|
||||
let alice_key = alice.agree(1, &static_secret, prev_alice_key.as_ref(), bob_public.as_slice());
|
||||
let bob_key = bob.agree(1, &static_secret, prev_bob_key.as_ref(), 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();
|
||||
assert_eq!(&alice_key.secret.key.0, &bob_key.secret.key.0);
|
||||
//println!("alice: c25519={} p521={} sidh={} | bob: c25519={} p521={} sidh={}", alice_key.c25519_ratchet_count, alice_key.nistp521_ratchet_count, alice_key.sidhp751_ratchet_count, bob_key.c25519_ratchet_count, bob_key.nistp521_ratchet_count, bob_key.sidhp751_ratchet_count);
|
||||
alice = EphemeralKeyPairSet::new(alice_address, bob_address, Some(&alice_key));
|
||||
bob = EphemeralKeyPairSet::new(bob_address, alice_address, Some(&bob_key));
|
||||
prev_alice_key = Some(alice_key);
|
||||
prev_bob_key = Some(bob_key);
|
||||
}
|
||||
let last_alice_key = prev_alice_key.unwrap();
|
||||
let last_bob_key = prev_bob_key.unwrap();
|
||||
assert_eq!(last_alice_key.c25519_ratchet_count, ratchets);
|
||||
assert_eq!(last_bob_key.c25519_ratchet_count, ratchets);
|
||||
assert_eq!(last_alice_key.nistp521_ratchet_count, ratchets);
|
||||
assert_eq!(last_bob_key.nistp521_ratchet_count, ratchets);
|
||||
assert_eq!(last_alice_key.sidhp751_ratchet_count, last_bob_key.sidhp751_ratchet_count);
|
||||
assert!(last_alice_key.sidhp751_ratchet_count >= 1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,9 +12,9 @@ use std::convert::TryInto;
|
|||
use std::hash::{Hash, Hasher};
|
||||
use std::io::Write;
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
use std::ptr::{slice_from_raw_parts, slice_from_raw_parts_mut};
|
||||
use std::str::FromStr;
|
||||
|
||||
use highway::HighwayHash;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use zerotier_core_crypto::aes_gmac_siv::AesGmacSiv;
|
||||
use zerotier_core_crypto::hash::SHA384_HASH_SIZE;
|
||||
use zerotier_core_crypto::kbkdf::zt_kbkdf_hmac_sha384;
|
||||
|
@ -38,7 +36,7 @@ pub(crate) struct SymmetricSecret {
|
|||
pub packet_hmac_key: Secret<SHA384_HASH_SIZE>,
|
||||
|
||||
/// A key used as input to the ephemeral key ratcheting mechanism.
|
||||
pub next_ephemeral_ratchet_key: Secret<SHA384_HASH_SIZE>,
|
||||
pub ephemeral_ratchet_key: Secret<SHA384_HASH_SIZE>,
|
||||
|
||||
/// A pool of reusable keyed and initialized AES-GMAC-SIV ciphers.
|
||||
pub aes_gmac_siv: Pool<AesGmacSiv, AesGmacSivPoolFactory>,
|
||||
|
@ -62,7 +60,7 @@ impl SymmetricSecret {
|
|||
SymmetricSecret {
|
||||
key: base_key,
|
||||
packet_hmac_key: usage_packet_hmac,
|
||||
next_ephemeral_ratchet_key: usage_ephemeral_ratchet,
|
||||
ephemeral_ratchet_key: usage_ephemeral_ratchet,
|
||||
aes_gmac_siv: Pool::new(2, aes_factory),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue