mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-04-26 17:03:43 +02:00
Add CareOf, which will be used with new roots in the near future. Also tweak some stuff like key sizes. There is no need for 512-bit keys when nothing uses the last 128 bits.
This commit is contained in:
parent
1df6843e94
commit
1da011b75e
7 changed files with 203 additions and 103 deletions
71
zerotier-network-hypervisor/src/vl1/careof.rs
Normal file
71
zerotier-network-hypervisor/src/vl1/careof.rs
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
// (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md.
|
||||||
|
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use crate::vl1::identity::Identity;
|
||||||
|
use crate::vl1::protocol::IDENTITY_FINGERPRINT_SIZE;
|
||||||
|
|
||||||
|
use zerotier_core_crypto::varint;
|
||||||
|
|
||||||
|
/// A signed bundle of identity fingerprints of nodes through which a node might be reached (e.g. roots).
|
||||||
|
///
|
||||||
|
/// This can be sent by nodes to indicate which other nodes they wish to have used to reach them. Typically
|
||||||
|
/// these would be roots. It prevents a misbehaving or malicious root from pretending to host a node.
|
||||||
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
|
pub struct CareOf {
|
||||||
|
pub timestamp: i64,
|
||||||
|
pub fingerprints: Vec<[u8; IDENTITY_FINGERPRINT_SIZE]>,
|
||||||
|
pub signature: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CareOf {
|
||||||
|
pub fn new(timestamp: i64) -> Self {
|
||||||
|
Self { timestamp, fingerprints: Vec::new(), signature: Vec::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_care_of(&mut self, id: &Identity) {
|
||||||
|
self.fingerprints.push(id.fingerprint);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_bytes_internal(&self, include_signature: bool) -> Vec<u8> {
|
||||||
|
let mut v: Vec<u8> = Vec::with_capacity(128 + (self.fingerprints.len() * 48));
|
||||||
|
let _ = varint::write(&mut v, self.timestamp as u64);
|
||||||
|
let _ = varint::write(&mut v, self.fingerprints.len() as u64);
|
||||||
|
for f in self.fingerprints.iter() {
|
||||||
|
let _ = v.write_all(f);
|
||||||
|
}
|
||||||
|
let _ = varint::write(&mut v, 0); // reserved for future use
|
||||||
|
if include_signature {
|
||||||
|
let _ = varint::write(&mut v, self.signature.len() as u64);
|
||||||
|
let _ = v.write_all(self.signature.as_slice());
|
||||||
|
}
|
||||||
|
v
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn to_bytes(&self) -> Vec<u8> {
|
||||||
|
self.to_bytes_internal(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sort, deduplicate, and sign this care-of packet.
|
||||||
|
///
|
||||||
|
/// The supplied identitiy must contain its secret keys. False is returned if there is an error.
|
||||||
|
pub fn sign(&mut self, signer: &Identity) -> bool {
|
||||||
|
self.fingerprints.sort_unstable();
|
||||||
|
self.fingerprints.dedup();
|
||||||
|
if let Some(sig) = signer.sign(self.to_bytes_internal(false).as_slice(), false) {
|
||||||
|
self.signature = sig;
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify(&self, signer: &Identity) -> bool {
|
||||||
|
signer.verify(self.to_bytes_internal(false).as_slice(), self.signature.as_slice())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn contains(&self, id: &Identity) -> bool {
|
||||||
|
self.fingerprints.binary_search(&id.fingerprint).is_ok()
|
||||||
|
}
|
||||||
|
}
|
|
@ -261,18 +261,15 @@ impl Identity {
|
||||||
///
|
///
|
||||||
/// If both sides have NIST P-384 keys then key agreement is performed using both Curve25519 and
|
/// If both sides have NIST P-384 keys then key agreement is performed using both Curve25519 and
|
||||||
/// NIST P-384 and the result is HMAC(Curve25519 secret, NIST P-384 secret).
|
/// NIST P-384 and the result is HMAC(Curve25519 secret, NIST P-384 secret).
|
||||||
///
|
pub fn agree(&self, other: &Identity) -> Option<Secret<48>> {
|
||||||
/// Nothing actually uses a 512-bit secret directly, but if the base secret is 512 bits then
|
|
||||||
/// no entropy is lost when deriving smaller secrets with a KDF.
|
|
||||||
pub fn agree(&self, other: &Identity) -> Option<Secret<64>> {
|
|
||||||
if let Some(secret) = self.secret.as_ref() {
|
if let Some(secret) = self.secret.as_ref() {
|
||||||
let c25519_secret = Secret(SHA512::hash(&secret.c25519.agree(&other.c25519).0));
|
let c25519_secret: Secret<48> = Secret((&SHA512::hash(&secret.c25519.agree(&other.c25519).0)[..48]).try_into().unwrap());
|
||||||
|
|
||||||
// FIPS note: FIPS-compliant exchange algorithms must be the last algorithms in any HKDF chain
|
// FIPS note: FIPS-compliant exchange algorithms must be the last algorithms in any HKDF chain
|
||||||
// for the final result to be technically FIPS compliant. Non-FIPS algorithm secrets are considered
|
// for the final result to be technically FIPS compliant. Non-FIPS algorithm secrets are considered
|
||||||
// a salt in the HMAC(salt, key) HKDF construction.
|
// a salt in the HMAC(salt, key) HKDF construction.
|
||||||
if secret.p384.is_some() && other.p384.is_some() {
|
if secret.p384.is_some() && other.p384.is_some() {
|
||||||
secret.p384.as_ref().unwrap().ecdh.agree(&other.p384.as_ref().unwrap().ecdh).map(|p384_secret| Secret(hmac_sha512(&c25519_secret.0, &p384_secret.0)))
|
secret.p384.as_ref().unwrap().ecdh.agree(&other.p384.as_ref().unwrap().ecdh).map(|p384_secret| Secret(hmac_sha384(&c25519_secret.0, &p384_secret.0)))
|
||||||
} else {
|
} else {
|
||||||
Some(c25519_secret)
|
Some(c25519_secret)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md.
|
// (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md.
|
||||||
|
|
||||||
mod address;
|
mod address;
|
||||||
|
mod careof;
|
||||||
mod dictionary;
|
mod dictionary;
|
||||||
mod endpoint;
|
mod endpoint;
|
||||||
mod fragmentedpacket;
|
mod fragmentedpacket;
|
||||||
|
|
|
@ -12,6 +12,7 @@ use parking_lot::{Mutex, RwLock};
|
||||||
use crate::error::InvalidParameterError;
|
use crate::error::InvalidParameterError;
|
||||||
use crate::util::debug_event;
|
use crate::util::debug_event;
|
||||||
use crate::util::gate::IntervalGate;
|
use crate::util::gate::IntervalGate;
|
||||||
|
use crate::vl1::careof::CareOf;
|
||||||
use crate::vl1::path::{Path, PathServiceResult};
|
use crate::vl1::path::{Path, PathServiceResult};
|
||||||
use crate::vl1::peer::Peer;
|
use crate::vl1::peer::Peer;
|
||||||
use crate::vl1::protocol::*;
|
use crate::vl1::protocol::*;
|
||||||
|
@ -133,8 +134,9 @@ struct BackgroundTaskIntervals {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RootInfo<SI: SystemInterface> {
|
struct RootInfo<SI: SystemInterface> {
|
||||||
roots: HashMap<Arc<Peer<SI>>, Vec<Endpoint>>,
|
|
||||||
sets: HashMap<String, RootSet>,
|
sets: HashMap<String, RootSet>,
|
||||||
|
roots: HashMap<Arc<Peer<SI>>, Vec<Endpoint>>,
|
||||||
|
care_of: Vec<u8>,
|
||||||
sets_modified: bool,
|
sets_modified: bool,
|
||||||
online: bool,
|
online: bool,
|
||||||
}
|
}
|
||||||
|
@ -255,8 +257,9 @@ impl<SI: SystemInterface> Node<SI> {
|
||||||
paths: parking_lot::RwLock::new(HashMap::new()),
|
paths: parking_lot::RwLock::new(HashMap::new()),
|
||||||
peers: parking_lot::RwLock::new(HashMap::new()),
|
peers: parking_lot::RwLock::new(HashMap::new()),
|
||||||
roots: RwLock::new(RootInfo {
|
roots: RwLock::new(RootInfo {
|
||||||
roots: HashMap::new(),
|
|
||||||
sets: HashMap::new(),
|
sets: HashMap::new(),
|
||||||
|
roots: HashMap::new(),
|
||||||
|
care_of: Vec::new(),
|
||||||
sets_modified: false,
|
sets_modified: false,
|
||||||
online: false,
|
online: false,
|
||||||
}),
|
}),
|
||||||
|
@ -415,7 +418,19 @@ impl<SI: SystemInterface> Node<SI> {
|
||||||
old_root_identities.sort_unstable();
|
old_root_identities.sort_unstable();
|
||||||
new_root_identities.sort_unstable();
|
new_root_identities.sort_unstable();
|
||||||
if !old_root_identities.eq(&new_root_identities) {
|
if !old_root_identities.eq(&new_root_identities) {
|
||||||
self.roots.write().roots = new_roots;
|
let mut care_of = CareOf::new(si.time_clock());
|
||||||
|
for id in new_root_identities.iter() {
|
||||||
|
care_of.add_care_of(id);
|
||||||
|
}
|
||||||
|
assert!(care_of.sign(&self.identity));
|
||||||
|
let care_of = care_of.to_bytes();
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut roots = self.roots.write();
|
||||||
|
roots.roots = new_roots;
|
||||||
|
roots.care_of = care_of;
|
||||||
|
}
|
||||||
|
|
||||||
si.event(Event::UpdatedRoots(old_root_identities, new_root_identities));
|
si.event(Event::UpdatedRoots(old_root_identities, new_root_identities));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -621,7 +636,11 @@ impl<SI: SystemInterface> Node<SI> {
|
||||||
self.roots.read().sets.values().cloned().collect()
|
self.roots.read().sets.values().cloned().collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn canonical_path(&self, ep: &Endpoint, local_socket: &SI::LocalSocket, local_interface: &SI::LocalInterface, time_ticks: i64) -> Arc<Path<SI>> {
|
pub(crate) fn care_of_bytes(&self) -> Vec<u8> {
|
||||||
|
self.roots.read().care_of.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn canonical_path(&self, ep: &Endpoint, local_socket: &SI::LocalSocket, local_interface: &SI::LocalInterface, time_ticks: i64) -> Arc<Path<SI>> {
|
||||||
if let Some(path) = self.paths.read().get(&PathKey::Ref(ep, local_socket)) {
|
if let Some(path) = self.paths.read().get(&PathKey::Ref(ep, local_socket)) {
|
||||||
return path.clone();
|
return path.clone();
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,7 @@ pub struct Peer<SI: SystemInterface> {
|
||||||
pub(crate) last_hello_reply_time_ticks: AtomicI64,
|
pub(crate) last_hello_reply_time_ticks: AtomicI64,
|
||||||
last_forward_time_ticks: AtomicI64,
|
last_forward_time_ticks: AtomicI64,
|
||||||
create_time_ticks: i64,
|
create_time_ticks: i64,
|
||||||
|
random_ticks_offset: u64,
|
||||||
|
|
||||||
// Counter for assigning sequential message IDs.
|
// Counter for assigning sequential message IDs.
|
||||||
message_id_counter: AtomicU64,
|
message_id_counter: AtomicU64,
|
||||||
|
@ -165,6 +166,7 @@ impl<SI: SystemInterface> Peer<SI> {
|
||||||
last_forward_time_ticks: AtomicI64::new(crate::util::NEVER_HAPPENED_TICKS),
|
last_forward_time_ticks: AtomicI64::new(crate::util::NEVER_HAPPENED_TICKS),
|
||||||
last_hello_reply_time_ticks: AtomicI64::new(crate::util::NEVER_HAPPENED_TICKS),
|
last_hello_reply_time_ticks: AtomicI64::new(crate::util::NEVER_HAPPENED_TICKS),
|
||||||
create_time_ticks: time_ticks,
|
create_time_ticks: time_ticks,
|
||||||
|
random_ticks_offset: next_u64_secure(),
|
||||||
message_id_counter: AtomicU64::new(((time_clock as u64) / 100).wrapping_shl(28) ^ next_u64_secure().wrapping_shr(36)),
|
message_id_counter: AtomicU64::new(((time_clock as u64) / 100).wrapping_shl(28) ^ next_u64_secure().wrapping_shr(36)),
|
||||||
remote_version: AtomicU64::new(0),
|
remote_version: AtomicU64::new(0),
|
||||||
remote_protocol_version: AtomicU8::new(0),
|
remote_protocol_version: AtomicU8::new(0),
|
||||||
|
@ -172,6 +174,27 @@ impl<SI: SystemInterface> Peer<SI> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the remote version of this peer: major, minor, revision, and build.
|
||||||
|
/// Returns None if it's not yet known.
|
||||||
|
pub fn version(&self) -> Option<[u16; 4]> {
|
||||||
|
let rv = self.remote_version.load(Ordering::Relaxed);
|
||||||
|
if rv != 0 {
|
||||||
|
Some([(rv >> 48) as u16, (rv >> 32) as u16, (rv >> 16) as u16, rv as u16])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the remote protocol version of this peer or None if not yet known.
|
||||||
|
pub fn protocol_version(&self) -> Option<u8> {
|
||||||
|
let pv = self.remote_protocol_version.load(Ordering::Relaxed);
|
||||||
|
if pv != 0 {
|
||||||
|
Some(pv)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the next message ID for sending a message to this peer.
|
/// Get the next message ID for sending a message to this peer.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn next_message_id(&self) -> MessageId {
|
pub(crate) fn next_message_id(&self) -> MessageId {
|
||||||
|
@ -201,32 +224,50 @@ impl<SI: SystemInterface> Peer<SI> {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the remote version of this peer: major, minor, revision, and build.
|
|
||||||
/// Returns None if it's not yet known.
|
|
||||||
pub fn version(&self) -> Option<[u16; 4]> {
|
|
||||||
let rv = self.remote_version.load(Ordering::Relaxed);
|
|
||||||
if rv != 0 {
|
|
||||||
Some([(rv >> 48) as u16, (rv >> 32) as u16, (rv >> 16) as u16, rv as u16])
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the remote protocol version of this peer or None if not yet known.
|
|
||||||
pub fn protocol_version(&self) -> Option<u8> {
|
|
||||||
let pv = self.remote_protocol_version.load(Ordering::Relaxed);
|
|
||||||
if pv != 0 {
|
|
||||||
Some(pv)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sort a list of paths by quality or priority, with best paths first.
|
/// Sort a list of paths by quality or priority, with best paths first.
|
||||||
fn prioritize_paths(paths: &mut Vec<PeerPath<SI>>) {
|
fn prioritize_paths(paths: &mut Vec<PeerPath<SI>>) {
|
||||||
paths.sort_unstable_by(|a, b| a.last_receive_time_ticks.cmp(&b.last_receive_time_ticks).reverse());
|
paths.sort_unstable_by(|a, b| a.last_receive_time_ticks.cmp(&b.last_receive_time_ticks).reverse());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn learn_path(&self, si: &SI, new_path: &Arc<Path<SI>>, time_ticks: i64) {
|
||||||
|
let mut paths = self.paths.lock();
|
||||||
|
|
||||||
|
// If this is an IpUdp endpoint, scan the existing paths and replace any that come from
|
||||||
|
// the same IP address but a different port. This prevents the accumulation of duplicate
|
||||||
|
// paths to the same peer over different ports.
|
||||||
|
match &new_path.endpoint {
|
||||||
|
Endpoint::IpUdp(new_ip) => {
|
||||||
|
for pi in paths.iter_mut() {
|
||||||
|
if let Some(p) = pi.path.upgrade() {
|
||||||
|
match &p.endpoint {
|
||||||
|
Endpoint::IpUdp(existing_ip) => {
|
||||||
|
if existing_ip.ip_bytes().eq(new_ip.ip_bytes()) {
|
||||||
|
debug_event!(si, "[vl1] {} replacing path {} with {} (same IP, different port)", self.identity.address.to_string(), p.endpoint.to_string(), new_path.endpoint.to_string());
|
||||||
|
pi.path = Arc::downgrade(new_path);
|
||||||
|
pi.canonical_instance_id = new_path.canonical.canonical_instance_id();
|
||||||
|
pi.last_receive_time_ticks = time_ticks;
|
||||||
|
Self::prioritize_paths(&mut paths);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise learn new path.
|
||||||
|
debug_event!(si, "[vl1] {} learned new path: {}", self.identity.address.to_string(), new_path.endpoint.to_string());
|
||||||
|
paths.push(PeerPath::<SI> {
|
||||||
|
path: Arc::downgrade(new_path),
|
||||||
|
canonical_instance_id: new_path.canonical.canonical_instance_id(),
|
||||||
|
last_receive_time_ticks: time_ticks,
|
||||||
|
});
|
||||||
|
Self::prioritize_paths(&mut paths);
|
||||||
|
}
|
||||||
|
|
||||||
/// Called every SERVICE_INTERVAL_MS by the background service loop in Node.
|
/// Called every SERVICE_INTERVAL_MS by the background service loop in Node.
|
||||||
pub(crate) fn service(&self, _: &SI, _: &Node<SI>, time_ticks: i64) -> bool {
|
pub(crate) fn service(&self, _: &SI, _: &Node<SI>, time_ticks: i64) -> bool {
|
||||||
{
|
{
|
||||||
|
@ -278,9 +319,9 @@ impl<SI: SystemInterface> Peer<SI> {
|
||||||
if let Ok(mut verb) = payload.u8_at(0) {
|
if let Ok(mut verb) = payload.u8_at(0) {
|
||||||
let extended_authentication = (verb & packet_constants::VERB_FLAG_EXTENDED_AUTHENTICATION) != 0;
|
let extended_authentication = (verb & packet_constants::VERB_FLAG_EXTENDED_AUTHENTICATION) != 0;
|
||||||
if extended_authentication {
|
if extended_authentication {
|
||||||
if payload.len() >= SHA512_HASH_SIZE {
|
if payload.len() >= SHA384_HASH_SIZE {
|
||||||
let actual_end_of_payload = payload.len() - SHA512_HASH_SIZE;
|
let actual_end_of_payload = payload.len() - SHA384_HASH_SIZE;
|
||||||
let mut hmac = HMACSHA512::new(self.identity_symmetric_key.packet_hmac_key.as_bytes());
|
let mut hmac = HMACSHA384::new(self.identity_symmetric_key.packet_hmac_key.as_bytes());
|
||||||
hmac.update(&node.identity.fingerprint);
|
hmac.update(&node.identity.fingerprint);
|
||||||
hmac.update(&self.identity.fingerprint);
|
hmac.update(&self.identity.fingerprint);
|
||||||
hmac.update(&message_id.to_ne_bytes());
|
hmac.update(&message_id.to_ne_bytes());
|
||||||
|
@ -331,7 +372,7 @@ impl<SI: SystemInterface> Peer<SI> {
|
||||||
//VERB_VL1_NOP => {}
|
//VERB_VL1_NOP => {}
|
||||||
verbs::VL1_HELLO => self.handle_incoming_hello(si, node, time_ticks, source_path, &payload).await,
|
verbs::VL1_HELLO => self.handle_incoming_hello(si, node, time_ticks, source_path, &payload).await,
|
||||||
verbs::VL1_ERROR => self.handle_incoming_error(si, ph, node, time_ticks, source_path, forward_secrecy, extended_authentication, &payload).await,
|
verbs::VL1_ERROR => self.handle_incoming_error(si, ph, node, time_ticks, source_path, forward_secrecy, extended_authentication, &payload).await,
|
||||||
verbs::VL1_OK => self.handle_incoming_ok(si, ph, node, time_ticks, source_path, path_is_known, forward_secrecy, extended_authentication, &payload).await,
|
verbs::VL1_OK => self.handle_incoming_ok(si, ph, node, time_ticks, source_path, packet_header.hops(), path_is_known, forward_secrecy, extended_authentication, &payload).await,
|
||||||
verbs::VL1_WHOIS => self.handle_incoming_whois(si, node, time_ticks, source_path, &payload).await,
|
verbs::VL1_WHOIS => self.handle_incoming_whois(si, node, time_ticks, source_path, &payload).await,
|
||||||
verbs::VL1_RENDEZVOUS => self.handle_incoming_rendezvous(si, node, time_ticks, source_path, &payload).await,
|
verbs::VL1_RENDEZVOUS => self.handle_incoming_rendezvous(si, node, time_ticks, source_path, &payload).await,
|
||||||
verbs::VL1_ECHO => self.handle_incoming_echo(si, node, time_ticks, source_path, &payload).await,
|
verbs::VL1_ECHO => self.handle_incoming_echo(si, node, time_ticks, source_path, &payload).await,
|
||||||
|
@ -463,7 +504,7 @@ impl<SI: SystemInterface> Peer<SI> {
|
||||||
hello_fixed_headers.version_major = VERSION_MAJOR;
|
hello_fixed_headers.version_major = VERSION_MAJOR;
|
||||||
hello_fixed_headers.version_minor = VERSION_MINOR;
|
hello_fixed_headers.version_minor = VERSION_MINOR;
|
||||||
hello_fixed_headers.version_revision = (VERSION_REVISION as u16).to_be_bytes();
|
hello_fixed_headers.version_revision = (VERSION_REVISION as u16).to_be_bytes();
|
||||||
hello_fixed_headers.timestamp = si.time_clock().to_be_bytes();
|
hello_fixed_headers.timestamp = (time_ticks as u64).wrapping_add(self.random_ticks_offset).to_be_bytes();
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(packet.len(), 41);
|
assert_eq!(packet.len(), 41);
|
||||||
|
@ -471,8 +512,16 @@ impl<SI: SystemInterface> Peer<SI> {
|
||||||
// Full identity of this node.
|
// Full identity of this node.
|
||||||
assert!(node.identity.marshal_with_options(&mut packet, Identity::ALGORITHM_ALL, false).is_ok());
|
assert!(node.identity.marshal_with_options(&mut packet, Identity::ALGORITHM_ALL, false).is_ok());
|
||||||
|
|
||||||
// Append two reserved bytes, currently always zero.
|
// Create session meta-data.
|
||||||
assert!(packet.append_padding(0, 2).is_ok());
|
let mut session_metadata = Dictionary::new();
|
||||||
|
session_metadata.set_bytes(session_metadata::INSTANCE_ID, node.instance_id.to_vec());
|
||||||
|
session_metadata.set_bytes(session_metadata::CARE_OF, node.care_of_bytes());
|
||||||
|
session_metadata.set_bytes(session_metadata::SENT_TO, destination.to_buffer::<{ Endpoint::MAX_MARSHAL_SIZE }>().unwrap().as_bytes().to_vec());
|
||||||
|
let session_metadata = session_metadata.to_bytes();
|
||||||
|
|
||||||
|
// Prefix encrypted session metadata with its size (in cleartext).
|
||||||
|
assert!(session_metadata.len() <= 0xffff); // sanity check, should be impossible
|
||||||
|
assert!(packet.append_u16(session_metadata.len() as u16).is_ok());
|
||||||
|
|
||||||
// Append a 16-byte AES-CTR nonce. LEGACY: for compatibility the last two bytes of this nonce
|
// Append a 16-byte AES-CTR nonce. LEGACY: for compatibility the last two bytes of this nonce
|
||||||
// are in fact an encryption of two zeroes with Salsa20/12, which old nodes will interpret as
|
// are in fact an encryption of two zeroes with Salsa20/12, which old nodes will interpret as
|
||||||
|
@ -483,22 +532,7 @@ impl<SI: SystemInterface> Peer<SI> {
|
||||||
Salsa::<12>::new(&self.identity_symmetric_key.key.0[0..32], &salsa_iv).crypt(&crate::util::ZEROES[..2], &mut nonce[14..]);
|
Salsa::<12>::new(&self.identity_symmetric_key.key.0[0..32], &salsa_iv).crypt(&crate::util::ZEROES[..2], &mut nonce[14..]);
|
||||||
assert!(packet.append_bytes_fixed(&nonce).is_ok());
|
assert!(packet.append_bytes_fixed(&nonce).is_ok());
|
||||||
|
|
||||||
// Add session meta-data, which is encrypted using plain AES-CTR. No authentication (AEAD) is needed
|
// Write session meta-data in encrypted form.
|
||||||
// because the whole packet is authenticated. While the data in this section is not necessarily critical
|
|
||||||
// to protect, encrypting it is a defense in depth measure.
|
|
||||||
let mut session_metadata = Dictionary::new();
|
|
||||||
session_metadata.set_bytes(session_metadata::INSTANCE_ID, node.instance_id.to_vec());
|
|
||||||
session_metadata.set_u64(session_metadata::TIME_TICKS, time_ticks as u64);
|
|
||||||
session_metadata.set_bytes(session_metadata::SENT_TO, destination.to_buffer::<{ Endpoint::MAX_MARSHAL_SIZE }>().unwrap().as_bytes().to_vec());
|
|
||||||
let session_metadata = session_metadata.to_bytes();
|
|
||||||
|
|
||||||
// Prefix encrypted session metadata with its size (in cleartext).
|
|
||||||
assert!(session_metadata.len() <= 0xffff); // sanity check, should be impossible
|
|
||||||
assert!(packet.append_u16(session_metadata.len() as u16).is_ok());
|
|
||||||
|
|
||||||
// Write session meta-data in encrypted form. A derived key is made using the message ID as a salt
|
|
||||||
// because this only ever uses the permanent identity key. In V2 we don't want to get near any
|
|
||||||
// key usage boundaries unless forward secrecy is off for some reason.
|
|
||||||
nonce[12] &= 0x7f; // mask off the MSB of the 32-bit counter part of the CTR nonce for compatibility with AES libraries that don't wrap
|
nonce[12] &= 0x7f; // mask off the MSB of the 32-bit counter part of the CTR nonce for compatibility with AES libraries that don't wrap
|
||||||
let salted_key = Secret(hmac_sha384(&message_id.to_ne_bytes(), self.identity_symmetric_key.hello_private_section_key.as_bytes()));
|
let salted_key = Secret(hmac_sha384(&message_id.to_ne_bytes(), self.identity_symmetric_key.hello_private_section_key.as_bytes()));
|
||||||
let mut aes = AesCtr::new(&salted_key.as_bytes()[0..32]);
|
let mut aes = AesCtr::new(&salted_key.as_bytes()[0..32]);
|
||||||
|
@ -506,12 +540,12 @@ impl<SI: SystemInterface> Peer<SI> {
|
||||||
aes.crypt(session_metadata.as_slice(), packet.append_bytes_get_mut(session_metadata.len()).unwrap());
|
aes.crypt(session_metadata.as_slice(), packet.append_bytes_get_mut(session_metadata.len()).unwrap());
|
||||||
|
|
||||||
// Set fragment flag if the packet will need to be fragmented.
|
// Set fragment flag if the packet will need to be fragmented.
|
||||||
if (packet.len() + SHA512_HASH_SIZE) > max_fragment_size {
|
if (packet.len() + SHA384_HASH_SIZE) > max_fragment_size {
|
||||||
set_packet_fragment_flag(&mut packet);
|
set_packet_fragment_flag(&mut packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seal packet with HMAC-SHA512 extended authentication.
|
// Seal packet with HMAC-SHA384 extended authentication.
|
||||||
let mut hmac = HMACSHA512::new(self.identity_symmetric_key.packet_hmac_key.as_bytes());
|
let mut hmac = HMACSHA384::new(self.identity_symmetric_key.packet_hmac_key.as_bytes());
|
||||||
hmac.update(&self.identity.fingerprint);
|
hmac.update(&self.identity.fingerprint);
|
||||||
hmac.update(&node.identity.fingerprint);
|
hmac.update(&node.identity.fingerprint);
|
||||||
hmac.update(&message_id.to_ne_bytes());
|
hmac.update(&message_id.to_ne_bytes());
|
||||||
|
@ -561,55 +595,34 @@ impl<SI: SystemInterface> Peer<SI> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
async fn handle_incoming_ok<PH: InnerProtocolInterface>(&self, si: &SI, ph: &PH, node: &Node<SI>, time_ticks: i64, source_path: &Arc<Path<SI>>, path_is_known: bool, forward_secrecy: bool, extended_authentication: bool, payload: &PacketBuffer) {
|
async fn handle_incoming_ok<PH: InnerProtocolInterface>(
|
||||||
|
&self,
|
||||||
|
si: &SI,
|
||||||
|
ph: &PH,
|
||||||
|
node: &Node<SI>,
|
||||||
|
time_ticks: i64,
|
||||||
|
source_path: &Arc<Path<SI>>,
|
||||||
|
hops: u8,
|
||||||
|
path_is_known: bool,
|
||||||
|
forward_secrecy: bool,
|
||||||
|
extended_authentication: bool,
|
||||||
|
payload: &PacketBuffer,
|
||||||
|
) {
|
||||||
let mut cursor: usize = 1;
|
let mut cursor: usize = 1;
|
||||||
if let Ok(ok_header) = payload.read_struct::<message_component_structs::OkHeader>(&mut cursor) {
|
if let Ok(ok_header) = payload.read_struct::<message_component_structs::OkHeader>(&mut cursor) {
|
||||||
let in_re_message_id = u64::from_ne_bytes(ok_header.in_re_message_id);
|
let in_re_message_id = u64::from_ne_bytes(ok_header.in_re_message_id);
|
||||||
let current_packet_id_counter = self.message_id_counter.load(Ordering::Relaxed);
|
let current_packet_id_counter = self.message_id_counter.load(Ordering::Relaxed);
|
||||||
if current_packet_id_counter.wrapping_sub(in_re_message_id) <= PACKET_RESPONSE_COUNTER_DELTA_MAX {
|
if current_packet_id_counter.wrapping_sub(in_re_message_id) <= PACKET_RESPONSE_COUNTER_DELTA_MAX {
|
||||||
// TODO: learn new paths
|
|
||||||
/*
|
|
||||||
if !path_is_known {
|
|
||||||
let mut paths = self.paths.lock();
|
|
||||||
paths.retain_mut(|p| {
|
|
||||||
if let Some(path) = p.path.upgrade() {
|
|
||||||
match (&path.endpoint, &source_path.endpoint) {
|
|
||||||
(Endpoint::IpUdp(current_address), Endpoint::IpUdp(source_address)) => {
|
|
||||||
if current_address.ip_bytes().eq(source_address.ip_bytes()) {
|
|
||||||
// In UDP mode, replace paths that come from the same IP but a different port. This helps avoid
|
|
||||||
// creating endless duplicate paths in NAT traversal scenarios if a NAT changes its port.
|
|
||||||
p.path = Arc::downgrade(source_path);
|
|
||||||
p.path_internal_instance_id = source_path.internal_instance_id;
|
|
||||||
p.last_receive_time_ticks = time_ticks;
|
|
||||||
path_is_known = true;
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => true,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if !path_is_known {
|
|
||||||
paths.push(PeerPath::<SI> {
|
|
||||||
path: Arc::downgrade(source_path),
|
|
||||||
path_internal_instance_id: source_path.internal_instance_id,
|
|
||||||
last_receive_time_ticks: time_ticks,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Self::prioritize_paths(&mut paths);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
match ok_header.in_re_verb {
|
match ok_header.in_re_verb {
|
||||||
verbs::VL1_HELLO => {
|
verbs::VL1_HELLO => {
|
||||||
// TODO
|
if hops == 0 && !path_is_known {
|
||||||
|
self.learn_path(si, source_path, time_ticks);
|
||||||
|
}
|
||||||
self.last_hello_reply_time_ticks.store(time_ticks, Ordering::Relaxed);
|
self.last_hello_reply_time_ticks.store(time_ticks, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
verbs::VL1_WHOIS => {}
|
verbs::VL1_WHOIS => {}
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
ph.handle_ok(self, &source_path, forward_secrecy, extended_authentication, ok_header.in_re_verb, in_re_message_id, payload, &mut cursor).await;
|
ph.handle_ok(self, &source_path, forward_secrecy, extended_authentication, ok_header.in_re_verb, in_re_message_id, payload, &mut cursor).await;
|
||||||
}
|
}
|
||||||
|
|
|
@ -234,8 +234,8 @@ pub mod security_constants {
|
||||||
|
|
||||||
pub mod session_metadata {
|
pub mod session_metadata {
|
||||||
pub const INSTANCE_ID: &'static str = "i";
|
pub const INSTANCE_ID: &'static str = "i";
|
||||||
pub const TIME_TICKS: &'static str = "t";
|
|
||||||
pub const SENT_TO: &'static str = "d";
|
pub const SENT_TO: &'static str = "d";
|
||||||
|
pub const CARE_OF: &'static str = "c";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Maximum number of packet hops allowed by the protocol.
|
/// Maximum number of packet hops allowed by the protocol.
|
||||||
|
|
|
@ -14,13 +14,13 @@ use crate::vl1::protocol::*;
|
||||||
/// This contains the key and several sub-keys and ciphers keyed with sub-keys.
|
/// This contains the key and several sub-keys and ciphers keyed with sub-keys.
|
||||||
pub(crate) struct SymmetricSecret {
|
pub(crate) struct SymmetricSecret {
|
||||||
/// Master key from which other keys are derived.
|
/// Master key from which other keys are derived.
|
||||||
pub key: Secret<64>,
|
pub key: Secret<48>,
|
||||||
|
|
||||||
/// Key for private fields in HELLO packets.
|
/// Key for private fields in HELLO packets.
|
||||||
pub hello_private_section_key: Secret<48>,
|
pub hello_private_section_key: Secret<48>,
|
||||||
|
|
||||||
/// Key used for HMAC extended validation on packets like HELLO.
|
/// Key used for HMAC extended validation on packets like HELLO.
|
||||||
pub packet_hmac_key: Secret<64>,
|
pub packet_hmac_key: Secret<48>,
|
||||||
|
|
||||||
/// Pool of keyed AES-GMAC-SIV engines (pooled to avoid AES re-init every time).
|
/// Pool of keyed AES-GMAC-SIV engines (pooled to avoid AES re-init every time).
|
||||||
pub aes_gmac_siv: Pool<AesGmacSiv, AesGmacSivPoolFactory>,
|
pub aes_gmac_siv: Pool<AesGmacSiv, AesGmacSivPoolFactory>,
|
||||||
|
@ -28,11 +28,10 @@ pub(crate) struct SymmetricSecret {
|
||||||
|
|
||||||
impl SymmetricSecret {
|
impl SymmetricSecret {
|
||||||
/// Create a new symmetric secret, deriving all sub-keys and such.
|
/// Create a new symmetric secret, deriving all sub-keys and such.
|
||||||
pub fn new(key: Secret<64>) -> SymmetricSecret {
|
pub fn new(key: Secret<48>) -> SymmetricSecret {
|
||||||
let hello_private_section_key = zt_kbkdf_hmac_sha384(&key.0, security_constants::KBKDF_KEY_USAGE_LABEL_HELLO_PRIVATE_SECTION);
|
let hello_private_section_key = zt_kbkdf_hmac_sha384(&key.0, security_constants::KBKDF_KEY_USAGE_LABEL_HELLO_PRIVATE_SECTION);
|
||||||
let packet_hmac_key = zt_kbkdf_hmac_sha512(&key.0, security_constants::KBKDF_KEY_USAGE_LABEL_PACKET_HMAC);
|
let packet_hmac_key = zt_kbkdf_hmac_sha384(&key.0, security_constants::KBKDF_KEY_USAGE_LABEL_PACKET_HMAC);
|
||||||
let aes_factory =
|
let aes_factory = AesGmacSivPoolFactory(zt_kbkdf_hmac_sha384(&key.0, security_constants::KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K0).first_n(), zt_kbkdf_hmac_sha384(&key.0[..48], security_constants::KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K1).first_n());
|
||||||
AesGmacSivPoolFactory(zt_kbkdf_hmac_sha384(&key.0[..48], security_constants::KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K0).first_n(), zt_kbkdf_hmac_sha384(&key.0[..48], security_constants::KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K1).first_n());
|
|
||||||
SymmetricSecret {
|
SymmetricSecret {
|
||||||
key,
|
key,
|
||||||
hello_private_section_key,
|
hello_private_section_key,
|
||||||
|
|
Loading…
Add table
Reference in a new issue