This commit is contained in:
Adam Ierymenko 2022-10-17 12:59:10 -04:00
parent 5006580c41
commit 23e73bbdd1
No known key found for this signature in database
GPG key ID: C8877CF2D7A5D7F3
6 changed files with 250 additions and 222 deletions

View file

@ -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<Public>,
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<P384PublicKey> {
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<Private>,
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<P384KeyPair> {
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<P384_SECRET_KEY_SIZE> {
let mut tmp: Secret<P384_SECRET_KEY_SIZE> = 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<Secret<P384_ECDH_SHARED_SECRET_SIZE>> {
unsafe {
let mut s: Secret<P384_ECDH_SHARED_SECRET_SIZE> = 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<Public>,
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<P384PublicKey> {
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<Private>,
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<P384KeyPair> {
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<P384_SECRET_KEY_SIZE> {
let mut tmp: Secret<P384_SECRET_KEY_SIZE> = 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<Secret<P384_ECDH_SHARED_SECRET_SIZE>> {
unsafe {
let mut s: Secret<P384_ECDH_SHARED_SECRET_SIZE> = 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::*;

View file

@ -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<E>(self, v: &str) -> Result<Self::Value, E>

View file

@ -239,7 +239,7 @@ impl<HostSystemImpl: HostSystem + ?Sized> Peer<HostSystemImpl> {
};
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;

View file

@ -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<Thing>,
pub issued_to: Address,
pub issued_to_fingerprint: Blob<{ Identity::FINGERPRINT_SIZE }>,
pub signature: ArrayVec<u8, { crate::vl1::identity::IDENTITY_MAX_SIGNATURE_SIZE }>,
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]);

View file

@ -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<IpRoute>,
#[serde(skip_serializing_if = "HashSet::is_empty")]
#[serde(default)]
pub static_ips: HashSet<InetAddress>,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
pub rules: Vec<Rule>,
#[serde(skip_serializing_if = "HashMap::is_empty")]
#[serde(default)]
pub dns: HashMap<String, HashSet<InetAddress>>,
pub certificate_of_membership: Option<CertificateOfMembership>, // considered invalid if None
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
pub certificates_of_ownership: Vec<CertificateOfOwnership>,
#[serde(skip_serializing_if = "HashMap::is_empty")]
#[serde(default)]
pub tags: HashMap<u32, Tag>,
pub banned: HashSet<Address>, // v2 only
#[serde(skip_serializing_if = "HashSet::is_empty")]
#[serde(default)]
pub banned: HashSet<Address>, // v2 only
#[serde(skip_serializing_if = "HashMap::is_empty")]
#[serde(default)]
pub node_info: HashMap<Address, NodeInfo>, // 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<SSOAuthConfiguration>,
}
@ -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<InetAddress>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub name: Option<String>,
#[serde(skip_serializing_if = "HashMap::is_empty")]
#[serde(default)]
pub services: HashMap<String, Option<String>>,
}
@ -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<InetAddress>,
pub flags: u16,
pub metric: u16,

View file

@ -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<u8, { identity::IDENTITY_MAX_SIGNATURE_SIZE }>,
pub version: u8,
}
@ -32,11 +32,11 @@ impl Tag {
legacy_v1: bool,
) -> Option<Self> {
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]);