From 23e73bbdd154139f989d5acf729c3ff96a00c69c Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Mon, 17 Oct 2022 12:59:10 -0400 Subject: [PATCH] cleanup --- crypto/src/p384.rs | 418 +++++++++--------- network-hypervisor/src/vl1/identity.rs | 2 +- network-hypervisor/src/vl1/peer.rs | 2 +- .../src/vl2/certificateofownership.rs | 4 - network-hypervisor/src/vl2/networkconfig.rs | 34 +- network-hypervisor/src/vl2/tag.rs | 12 +- 6 files changed, 250 insertions(+), 222 deletions(-) diff --git a/crypto/src/p384.rs b/crypto/src/p384.rs index 9726fb917..a43076c79 100644 --- a/crypto/src/p384.rs +++ b/crypto/src/p384.rs @@ -15,6 +15,215 @@ pub const P384_SECRET_KEY_SIZE: usize = 48; pub const P384_ECDSA_SIGNATURE_SIZE: usize = 96; pub const P384_ECDH_SHARED_SECRET_SIZE: usize = 48; +// Version using OpenSSL's ECC +#[cfg(not(target_feature = "builtin_nist_ecc"))] +mod openssl_based { + use std::convert::TryInto; + use std::os::raw::{c_int, c_ulong, c_void}; + use std::ptr::{null, write_volatile}; + + use foreign_types::{ForeignType, ForeignTypeRef}; + use lazy_static::lazy_static; + use openssl::bn::{BigNum, BigNumContext}; + use openssl::ec::{EcKey, EcPoint, EcPointRef, PointConversionForm}; + use openssl::ecdsa::EcdsaSig; + use openssl::nid::Nid; + use openssl::pkey::{Private, Public}; + + use crate::hash::SHA384; + use crate::secret::Secret; + + use super::{P384_ECDH_SHARED_SECRET_SIZE, P384_ECDSA_SIGNATURE_SIZE, P384_PUBLIC_KEY_SIZE, P384_SECRET_KEY_SIZE}; + + //#[link(name="crypto")] + extern "C" { + fn ECDH_compute_key(out: *mut c_void, outlen: c_ulong, pub_key: *mut c_void, ecdh: *mut c_void, kdf: *const c_void) -> c_int; + } + + lazy_static! { + static ref GROUP_P384: openssl::ec::EcGroup = openssl::ec::EcGroup::from_curve_name(Nid::SECP384R1).unwrap(); + } + + /// A NIST P-384 ECDH/ECDSA public key. + #[derive(Clone)] + pub struct P384PublicKey { + key: EcKey, + bytes: [u8; 49], + } + + impl P384PublicKey { + fn new_from_point(key: &EcPointRef) -> Self { + let mut bnc = BigNumContext::new().unwrap(); + let kb = key + .to_bytes(GROUP_P384.as_ref(), PointConversionForm::COMPRESSED, &mut bnc) + .unwrap(); + let mut bytes = [0_u8; 49]; + bytes[(49 - kb.len())..].copy_from_slice(kb.as_slice()); + Self { + key: EcKey::from_public_key(GROUP_P384.as_ref(), key).unwrap(), + bytes, + } + } + + pub fn from_bytes(b: &[u8]) -> Option { + if b.len() == 49 { + let mut bnc = BigNumContext::new().unwrap(); + let key = EcPoint::from_bytes(GROUP_P384.as_ref(), b, &mut bnc); + if key.is_ok() { + let key = key.unwrap(); + if key.is_on_curve(GROUP_P384.as_ref(), &mut bnc).unwrap_or(false) { + let key = EcKey::from_public_key(GROUP_P384.as_ref(), key.as_ref()); + if key.is_ok() { + return Some(Self { key: key.unwrap(), bytes: b.try_into().unwrap() }); + } + } + } + } + return None; + } + + pub fn verify(&self, msg: &[u8], signature: &[u8]) -> bool { + if signature.len() == 96 { + let r = BigNum::from_slice(&signature[0..48]); + let s = BigNum::from_slice(&signature[48..96]); + if r.is_ok() && s.is_ok() { + let r = r.unwrap(); + let s = s.unwrap(); + let z = BigNum::from_u32(0).unwrap(); + // Check that r and s are >=1 just in case the OpenSSL version or an OpenSSL API lookalike is + // vulnerable to this, since a bunch of vulnerabilities involving zero r/s just made the rounds. + if r.gt(&z) && s.gt(&z) { + let sig = EcdsaSig::from_private_components(r, s); + if sig.is_ok() { + return sig.unwrap().verify(&SHA384::hash(msg), self.key.as_ref()).unwrap_or(false); + } + } + } + } + return false; + } + + pub fn as_bytes(&self) -> &[u8; 49] { + &self.bytes + } + } + + impl PartialEq for P384PublicKey { + fn eq(&self, other: &Self) -> bool { + self.bytes == other.bytes + } + } + + unsafe impl Send for P384PublicKey {} + + unsafe impl Sync for P384PublicKey {} + + /// A NIST P-384 ECDH/ECDSA public/private key pair. + #[derive(Clone)] + pub struct P384KeyPair { + pair: EcKey, + public: P384PublicKey, + } + + impl P384KeyPair { + pub fn generate() -> P384KeyPair { + let pair = EcKey::generate(GROUP_P384.as_ref()).unwrap(); // failure implies a serious problem + assert!(pair.check_key().is_ok()); // also would imply a serious problem + let public = P384PublicKey::new_from_point(pair.public_key()); + Self { pair, public } + } + + pub fn from_bytes(public_bytes: &[u8], secret_bytes: &[u8]) -> Option { + if public_bytes.len() == 49 && secret_bytes.len() == 48 { + P384PublicKey::from_bytes(public_bytes).map_or(None, |public| { + BigNum::from_slice(secret_bytes).map_or(None, |private| { + let pair = EcKey::from_private_components(GROUP_P384.as_ref(), private.as_ref(), public.key.public_key()); + if pair.is_ok() { + let pair = pair.unwrap(); + if pair.check_key().is_ok() { + Some(Self { pair, public }) + } else { + None + } + } else { + None + } + }) + }) + } else { + None + } + } + + pub fn public_key(&self) -> &P384PublicKey { + &self.public + } + + pub fn public_key_bytes(&self) -> &[u8; P384_PUBLIC_KEY_SIZE] { + &self.public.bytes + } + + pub fn secret_key_bytes(&self) -> Secret { + let mut tmp: Secret = Secret::default(); + let mut k = self.pair.private_key().to_vec(); + tmp.0[(48 - k.len())..].copy_from_slice(k.as_slice()); + unsafe { + // Force zero memory occupied by temporary vector before releasing. + let kp = k.as_mut_ptr(); + for i in 0..k.len() { + write_volatile(kp.add(i), 0); + } + } + tmp + } + + /// Sign a message with ECDSA/SHA384. + pub fn sign(&self, msg: &[u8]) -> [u8; P384_ECDSA_SIGNATURE_SIZE] { + let sig = EcdsaSig::sign(&SHA384::hash(msg), self.pair.as_ref()).unwrap(); + let r = sig.r().to_vec(); + let s = sig.s().to_vec(); + assert!(!r.is_empty() && !s.is_empty() && r.len() <= 48 && s.len() <= 48); + let mut b = [0_u8; P384_ECDSA_SIGNATURE_SIZE]; + b[(48 - r.len())..48].copy_from_slice(r.as_slice()); + b[(96 - s.len())..96].copy_from_slice(s.as_slice()); + b + } + + /// Perform ECDH key agreement, returning the raw (un-hashed!) ECDH secret. + /// + /// This secret should not be used directly. It should be hashed and perhaps used in a KDF. + pub fn agree(&self, other_public: &P384PublicKey) -> Option> { + unsafe { + let mut s: Secret = Secret::default(); + if ECDH_compute_key( + s.0.as_mut_ptr().cast(), + 48, + other_public.key.public_key().as_ptr().cast(), + self.pair.as_ptr().cast(), + null(), + ) == 48 + { + Some(s) + } else { + None + } + } + } + } + + impl PartialEq for P384KeyPair { + fn eq(&self, other: &Self) -> bool { + self.pair.private_key().eq(other.pair.private_key()) && self.public.bytes.eq(&other.public.bytes) + } + } + + impl Eq for P384KeyPair {} + + unsafe impl Send for P384KeyPair {} + + unsafe impl Sync for P384KeyPair {} +} + // This is small and relatively fast but may not be constant time and hasn't been well audited, so we don't // use it by default. It's left here though in case it proves useful in the future on embedded systems. #[cfg(target_feature = "builtin_nist_ecc")] @@ -1138,215 +1347,6 @@ mod builtin { impl P384KeyPair {} } -// Version using OpenSSL's ECC -#[cfg(not(target_feature = "builtin_nist_ecc"))] -mod openssl_based { - use std::convert::TryInto; - use std::os::raw::{c_int, c_ulong, c_void}; - use std::ptr::{null, write_volatile}; - - use foreign_types::{ForeignType, ForeignTypeRef}; - use lazy_static::lazy_static; - use openssl::bn::{BigNum, BigNumContext}; - use openssl::ec::{EcKey, EcPoint, EcPointRef, PointConversionForm}; - use openssl::ecdsa::EcdsaSig; - use openssl::nid::Nid; - use openssl::pkey::{Private, Public}; - - use crate::hash::SHA384; - use crate::secret::Secret; - - use super::{P384_ECDH_SHARED_SECRET_SIZE, P384_ECDSA_SIGNATURE_SIZE, P384_PUBLIC_KEY_SIZE, P384_SECRET_KEY_SIZE}; - - //#[link(name="crypto")] - extern "C" { - fn ECDH_compute_key(out: *mut c_void, outlen: c_ulong, pub_key: *mut c_void, ecdh: *mut c_void, kdf: *const c_void) -> c_int; - } - - lazy_static! { - static ref GROUP_P384: openssl::ec::EcGroup = openssl::ec::EcGroup::from_curve_name(Nid::SECP384R1).unwrap(); - } - - /// A NIST P-384 ECDH/ECDSA public key. - #[derive(Clone)] - pub struct P384PublicKey { - key: EcKey, - bytes: [u8; 49], - } - - impl P384PublicKey { - fn new_from_point(key: &EcPointRef) -> Self { - let mut bnc = BigNumContext::new().unwrap(); - let kb = key - .to_bytes(GROUP_P384.as_ref(), PointConversionForm::COMPRESSED, &mut bnc) - .unwrap(); - let mut bytes = [0_u8; 49]; - bytes[(49 - kb.len())..].copy_from_slice(kb.as_slice()); - Self { - key: EcKey::from_public_key(GROUP_P384.as_ref(), key).unwrap(), - bytes, - } - } - - pub fn from_bytes(b: &[u8]) -> Option { - if b.len() == 49 { - let mut bnc = BigNumContext::new().unwrap(); - let key = EcPoint::from_bytes(GROUP_P384.as_ref(), b, &mut bnc); - if key.is_ok() { - let key = key.unwrap(); - if key.is_on_curve(GROUP_P384.as_ref(), &mut bnc).unwrap_or(false) { - let key = EcKey::from_public_key(GROUP_P384.as_ref(), key.as_ref()); - if key.is_ok() { - return Some(Self { key: key.unwrap(), bytes: b.try_into().unwrap() }); - } - } - } - } - return None; - } - - pub fn verify(&self, msg: &[u8], signature: &[u8]) -> bool { - if signature.len() == 96 { - let r = BigNum::from_slice(&signature[0..48]); - let s = BigNum::from_slice(&signature[48..96]); - if r.is_ok() && s.is_ok() { - let r = r.unwrap(); - let s = s.unwrap(); - let z = BigNum::from_u32(0).unwrap(); - // Check that r and s are >=1 just in case the OpenSSL version or an OpenSSL API lookalike is - // vulnerable to this, since a bunch of vulnerabilities involving zero r/s just made the rounds. - if r.gt(&z) && s.gt(&z) { - let sig = EcdsaSig::from_private_components(r, s); - if sig.is_ok() { - return sig.unwrap().verify(&SHA384::hash(msg), self.key.as_ref()).unwrap_or(false); - } - } - } - } - return false; - } - - pub fn as_bytes(&self) -> &[u8; 49] { - &self.bytes - } - } - - impl PartialEq for P384PublicKey { - fn eq(&self, other: &Self) -> bool { - self.bytes == other.bytes - } - } - - unsafe impl Send for P384PublicKey {} - - unsafe impl Sync for P384PublicKey {} - - /// A NIST P-384 ECDH/ECDSA public/private key pair. - #[derive(Clone)] - pub struct P384KeyPair { - pair: EcKey, - public: P384PublicKey, - } - - impl P384KeyPair { - pub fn generate() -> P384KeyPair { - let pair = EcKey::generate(GROUP_P384.as_ref()).unwrap(); // failure implies a serious problem - assert!(pair.check_key().is_ok()); // also would imply a serious problem - let public = P384PublicKey::new_from_point(pair.public_key()); - Self { pair, public } - } - - pub fn from_bytes(public_bytes: &[u8], secret_bytes: &[u8]) -> Option { - if public_bytes.len() == 49 && secret_bytes.len() == 48 { - P384PublicKey::from_bytes(public_bytes).map_or(None, |public| { - BigNum::from_slice(secret_bytes).map_or(None, |private| { - let pair = EcKey::from_private_components(GROUP_P384.as_ref(), private.as_ref(), public.key.public_key()); - if pair.is_ok() { - let pair = pair.unwrap(); - if pair.check_key().is_ok() { - Some(Self { pair, public }) - } else { - None - } - } else { - None - } - }) - }) - } else { - None - } - } - - pub fn public_key(&self) -> &P384PublicKey { - &self.public - } - - pub fn public_key_bytes(&self) -> &[u8; P384_PUBLIC_KEY_SIZE] { - &self.public.bytes - } - - pub fn secret_key_bytes(&self) -> Secret { - let mut tmp: Secret = Secret::default(); - let mut k = self.pair.private_key().to_vec(); - tmp.0[(48 - k.len())..].copy_from_slice(k.as_slice()); - unsafe { - // Force zero memory occupied by temporary vector before releasing. - let kp = k.as_mut_ptr(); - for i in 0..k.len() { - write_volatile(kp.add(i), 0); - } - } - tmp - } - - /// Sign a message with ECDSA/SHA384. - pub fn sign(&self, msg: &[u8]) -> [u8; P384_ECDSA_SIGNATURE_SIZE] { - let sig = EcdsaSig::sign(&SHA384::hash(msg), self.pair.as_ref()).unwrap(); - let r = sig.r().to_vec(); - let s = sig.s().to_vec(); - assert!(!r.is_empty() && !s.is_empty() && r.len() <= 48 && s.len() <= 48); - let mut b = [0_u8; P384_ECDSA_SIGNATURE_SIZE]; - b[(48 - r.len())..48].copy_from_slice(r.as_slice()); - b[(96 - s.len())..96].copy_from_slice(s.as_slice()); - b - } - - /// Perform ECDH key agreement, returning the raw (un-hashed!) ECDH secret. - /// - /// This secret should not be used directly. It should be hashed and perhaps used in a KDF. - pub fn agree(&self, other_public: &P384PublicKey) -> Option> { - unsafe { - let mut s: Secret = Secret::default(); - if ECDH_compute_key( - s.0.as_mut_ptr().cast(), - 48, - other_public.key.public_key().as_ptr().cast(), - self.pair.as_ptr().cast(), - null(), - ) == 48 - { - Some(s) - } else { - None - } - } - } - } - - impl PartialEq for P384KeyPair { - fn eq(&self, other: &Self) -> bool { - self.pair.private_key().eq(other.pair.private_key()) && self.public.bytes.eq(&other.public.bytes) - } - } - - impl Eq for P384KeyPair {} - - unsafe impl Send for P384KeyPair {} - - unsafe impl Sync for P384KeyPair {} -} - #[cfg(target_feature = "builtin_nist_ecc")] pub use builtin::*; diff --git a/network-hypervisor/src/vl1/identity.rs b/network-hypervisor/src/vl1/identity.rs index 5a516645c..638c85685 100644 --- a/network-hypervisor/src/vl1/identity.rs +++ b/network-hypervisor/src/vl1/identity.rs @@ -831,7 +831,7 @@ impl<'de> serde::de::Visitor<'de> for IdentityVisitor { where E: serde::de::Error, { - todo!() + Identity::from_bytes(v).map_err(|e| serde::de::Error::custom(e.to_string())) } fn visit_str(self, v: &str) -> Result diff --git a/network-hypervisor/src/vl1/peer.rs b/network-hypervisor/src/vl1/peer.rs index e0402649c..3e406aa9a 100644 --- a/network-hypervisor/src/vl1/peer.rs +++ b/network-hypervisor/src/vl1/peer.rs @@ -239,7 +239,7 @@ impl Peer { }; let mut chunk_size = (packet_size - pos).min(UDP_DEFAULT_MTU - v1::HEADER_SIZE); - let mut tmp_buf: [u8; v1::SIZE_MAX] = unsafe { std::mem::MaybeUninit::uninit().assume_init() }; + let mut tmp_buf = [0u8; v1::SIZE_MAX]; loop { header.total_and_fragment_no += 1; let next_pos = pos + chunk_size; diff --git a/network-hypervisor/src/vl2/certificateofownership.rs b/network-hypervisor/src/vl2/certificateofownership.rs index 7f04cf122..2a76efeef 100644 --- a/network-hypervisor/src/vl2/certificateofownership.rs +++ b/network-hypervisor/src/vl2/certificateofownership.rs @@ -7,7 +7,6 @@ use crate::vl2::NetworkId; use serde::{Deserialize, Serialize}; use zerotier_utils::arrayvec::ArrayVec; -use zerotier_utils::blob::Blob; use zerotier_utils::error::InvalidParameterError; #[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] @@ -34,7 +33,6 @@ pub struct CertificateOfOwnership { pub timestamp: i64, pub things: HashSet, pub issued_to: Address, - pub issued_to_fingerprint: Blob<{ Identity::FINGERPRINT_SIZE }>, pub signature: ArrayVec, pub version: u8, } @@ -47,7 +45,6 @@ impl CertificateOfOwnership { timestamp, things: HashSet::with_capacity(4), issued_to, - issued_to_fingerprint: Blob::default(), signature: ArrayVec::new(), version: if legacy_v1 { 1 @@ -165,7 +162,6 @@ impl CertificateOfOwnership { timestamp, things, issued_to: Address::from_bytes(&b[..5]).ok_or(InvalidParameterError("invalid address"))?, - issued_to_fingerprint: Blob::default(), signature: { let mut s = ArrayVec::new(); s.push_slice(&b[13..109]); diff --git a/network-hypervisor/src/vl2/networkconfig.rs b/network-hypervisor/src/vl2/networkconfig.rs index 5237c21dd..dba24159a 100644 --- a/network-hypervisor/src/vl2/networkconfig.rs +++ b/network-hypervisor/src/vl2/networkconfig.rs @@ -24,7 +24,11 @@ pub struct NetworkConfig { pub network_id: NetworkId, pub issued_to: Address, + #[serde(skip_serializing_if = "String::is_empty")] + #[serde(default)] pub name: String, + #[serde(skip_serializing_if = "String::is_empty")] + #[serde(default)] pub motd: String, pub private: bool, @@ -34,19 +38,39 @@ pub struct NetworkConfig { pub mtu: u16, pub multicast_limit: u32, + #[serde(skip_serializing_if = "HashSet::is_empty")] + #[serde(default)] pub routes: HashSet, + #[serde(skip_serializing_if = "HashSet::is_empty")] + #[serde(default)] pub static_ips: HashSet, + #[serde(skip_serializing_if = "Vec::is_empty")] + #[serde(default)] pub rules: Vec, + #[serde(skip_serializing_if = "HashMap::is_empty")] + #[serde(default)] pub dns: HashMap>, pub certificate_of_membership: Option, // considered invalid if None + #[serde(skip_serializing_if = "Vec::is_empty")] + #[serde(default)] pub certificates_of_ownership: Vec, + #[serde(skip_serializing_if = "HashMap::is_empty")] + #[serde(default)] pub tags: HashMap, - pub banned: HashSet
, // v2 only + #[serde(skip_serializing_if = "HashSet::is_empty")] + #[serde(default)] + pub banned: HashSet
, // v2 only + #[serde(skip_serializing_if = "HashMap::is_empty")] + #[serde(default)] pub node_info: HashMap, // v2 only + #[serde(skip_serializing_if = "String::is_empty")] + #[serde(default)] pub central_url: String, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] pub sso: Option, } @@ -387,8 +411,14 @@ pub struct SSOAuthConfiguration { #[derive(Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct NodeInfo { pub flags: u64, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] pub ip: Option, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] pub name: Option, + #[serde(skip_serializing_if = "HashMap::is_empty")] + #[serde(default)] pub services: HashMap>, } @@ -396,6 +426,8 @@ pub struct NodeInfo { #[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] pub struct IpRoute { pub target: InetAddress, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] pub via: Option, pub flags: u16, pub metric: u16, diff --git a/network-hypervisor/src/vl2/tag.rs b/network-hypervisor/src/vl2/tag.rs index b0c975495..ffae0a1a1 100644 --- a/network-hypervisor/src/vl2/tag.rs +++ b/network-hypervisor/src/vl2/tag.rs @@ -12,11 +12,11 @@ use zerotier_utils::error::InvalidParameterError; #[derive(Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct Tag { - pub id: u32, - pub value: u32, pub network_id: NetworkId, pub timestamp: i64, pub issued_to: Address, + pub id: u32, + pub value: u32, pub signature: ArrayVec, pub version: u8, } @@ -32,11 +32,11 @@ impl Tag { legacy_v1: bool, ) -> Option { let mut tag = Self { - id, - value, network_id, timestamp, issued_to: issued_to.address, + id, + value, signature: ArrayVec::new(), version: if legacy_v1 { 1 @@ -107,11 +107,11 @@ impl Tag { } Ok(( Self { - id: u32::from_be_bytes(b[16..20].try_into().unwrap()), - value: u32::from_be_bytes(b[20..24].try_into().unwrap()), network_id: NetworkId::from_bytes(&b[0..8]).ok_or(InvalidParameterError("invalid network ID"))?, timestamp: i64::from_be_bytes(b[8..16].try_into().unwrap()), issued_to: Address::from_bytes(&b[24..29]).ok_or(InvalidParameterError("invalid address"))?, + id: u32::from_be_bytes(b[16..20].try_into().unwrap()), + value: u32::from_be_bytes(b[20..24].try_into().unwrap()), signature: { let mut s = ArrayVec::new(); s.push_slice(&b[37..133]);