mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-06-06 12:33:44 +02:00
silence compiler warnings about _unused_variables (#1852)
Tetanus noise xk (#1881) * Noise XK work in progress. * A whole lot more Noise_XK work... exchange almost done. * Delete a bunch of commented out old Noise_IK code. * Add back in send() and a few other things to Noise_XK ZSSP. * Some p384 experiment in attic * A ton of ZSSP work, and put MPL on ZSSP. * updated kbkdf512 to use the modern nist standard * Parameterize KBKDF on resulting output key size the way NIST likes. * updated variable comment * Make the label a const parameter on kbkdf. * updated variable comment * Add MPL to utils and other stuff. * layout tweak * Some more ZSSP work and a VDF we may use. * warning removal * More ZSSP work, add benchmarks for mimcvdf. * Almost ready to test... * Build fix. * Add automatic retransmission in the earliest stages of session init. * Just about ready to test... wow. * It opens a session. * ZSSP basically works... --------- Co-authored-by: mamoniot <mamoniot@protonmail.com> Warning removal. remove old docs Remove old tests from ZSSP, new test in main() can also be made into a unit test in the future. Add key_info() to get key information. Rekeying is now tested and works. Show key fingerprint. Factor out memory:: stuff, does not appear to have any real performance benefit. Rework defragmentation, and it now tolerates very poor link quality pretty well. Circuit breaker for incoming defrag queue, and ZSSP now works very well even under very poor network conditions. Format tweak. ZSSP API updates. Just a bit of final ZSSP cleanup before moving to another thing.
This commit is contained in:
parent
b622475357
commit
0c91b75bbd
60 changed files with 2757 additions and 1997 deletions
|
@ -67,9 +67,7 @@ impl Controller {
|
|||
recently_authorized: RwLock::new(HashMap::new()),
|
||||
}))
|
||||
} else {
|
||||
Err(Box::new(InvalidParameterError(
|
||||
"local controller's identity not readable by database",
|
||||
)))
|
||||
Err(Box::new(InvalidParameterError("local controller's identity not readable by database")))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -238,8 +236,7 @@ impl Controller {
|
|||
if member.node_id != *m {
|
||||
if let Some(peer) = self.service.read().unwrap().upgrade().and_then(|s| s.node().peer(*m)) {
|
||||
revocations.clear();
|
||||
Revocation::new(member.network_id, time_clock, member.node_id, *m, &self.local_identity, false)
|
||||
.map(|r| revocations.push(r));
|
||||
Revocation::new(member.network_id, time_clock, member.node_id, *m, &self.local_identity, false).map(|r| revocations.push(r));
|
||||
self.send_revocations(&peer, &mut revocations);
|
||||
}
|
||||
}
|
||||
|
@ -565,8 +562,7 @@ impl InnerProtocolLayer for Controller {
|
|||
};
|
||||
|
||||
// Launch handler as an async background task.
|
||||
let (self2, source, source_remote_endpoint) =
|
||||
(self.self_ref.upgrade().unwrap(), source.clone(), source_path.endpoint.clone());
|
||||
let (self2, source, source_remote_endpoint) = (self.self_ref.upgrade().unwrap(), source.clone(), source_path.endpoint.clone());
|
||||
self.reaper.add(
|
||||
self.runtime.spawn(async move {
|
||||
let node_id = source.identity.address;
|
||||
|
|
|
@ -50,8 +50,8 @@ impl FileDatabase {
|
|||
let db_weak = db_weak_tmp.clone();
|
||||
let runtime2 = runtime.clone();
|
||||
|
||||
let local_identity = load_node_identity(base_path.as_path())
|
||||
.ok_or(std::io::Error::new(std::io::ErrorKind::NotFound, "identity.secret not found"))?;
|
||||
let local_identity =
|
||||
load_node_identity(base_path.as_path()).ok_or(std::io::Error::new(std::io::ErrorKind::NotFound, "identity.secret not found"))?;
|
||||
let controller_address = local_identity.address;
|
||||
|
||||
let db = Arc::new(Self {
|
||||
|
@ -144,8 +144,7 @@ impl FileDatabase {
|
|||
.send(Change::NetworkChanged(old_network, new_network));
|
||||
}
|
||||
(true, None) => {
|
||||
let _ =
|
||||
db.change_sender.send(Change::NetworkCreated(new_network));
|
||||
let _ = db.change_sender.send(Change::NetworkCreated(new_network));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
@ -155,13 +154,11 @@ impl FileDatabase {
|
|||
if let Ok(Some(new_member)) = Self::load_object::<Member>(changed).await {
|
||||
match db.cache.on_member_updated(new_member.clone()) {
|
||||
(true, Some(old_member)) => {
|
||||
let _ = db
|
||||
.change_sender
|
||||
.send(Change::MemberChanged(old_member, new_member));
|
||||
let _ =
|
||||
db.change_sender.send(Change::MemberChanged(old_member, new_member));
|
||||
}
|
||||
(true, None) => {
|
||||
let _ =
|
||||
db.change_sender.send(Change::MemberCreated(new_member));
|
||||
let _ = db.change_sender.send(Change::MemberCreated(new_member));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
@ -315,9 +312,7 @@ impl Database for FileDatabase {
|
|||
if ent.file_type().await.map_or(false, |t| t.is_file() || t.is_symlink()) {
|
||||
let osname = ent.file_name();
|
||||
let name = osname.to_string_lossy();
|
||||
if name.len() == (zerotier_network_hypervisor::protocol::ADDRESS_SIZE_STRING + 6)
|
||||
&& name.starts_with("M")
|
||||
&& name.ends_with(".yaml")
|
||||
if name.len() == (zerotier_network_hypervisor::protocol::ADDRESS_SIZE_STRING + 6) && name.starts_with("M") && name.ends_with(".yaml")
|
||||
{
|
||||
if let Ok(member_address) = u64::from_str_radix(&name[1..11], 16) {
|
||||
if let Some(member_address) = Address::from_u64(member_address) {
|
||||
|
|
|
@ -88,18 +88,13 @@ impl PostgresConnection {
|
|||
let (client, connection) = tokio_postgres::connect(postgres_path, tokio_postgres::NoTls).await?;
|
||||
Ok(Box::new(Self {
|
||||
s_list_networks: client
|
||||
.prepare_typed(
|
||||
"SELECT id FROM ztc_network WHERE controller_id = $1 AND deleted = false",
|
||||
&[Type::TEXT],
|
||||
)
|
||||
.prepare_typed("SELECT id FROM ztc_network WHERE controller_id = $1 AND deleted = false", &[Type::TEXT])
|
||||
.await?,
|
||||
s_list_members: client
|
||||
.prepare_typed("SELECT id FROM ztc_member WHERE network_id = $1 AND deleted = false", &[Type::TEXT])
|
||||
.await?,
|
||||
s_get_network: client.prepare_typed(GET_NETWORK_SQL, &[Type::TEXT]).await?,
|
||||
s_get_network_members_with_capabilities: client
|
||||
.prepare_typed(GET_NETWORK_MEMBERS_WITH_CAPABILITIES_SQL, &[Type::TEXT])
|
||||
.await?,
|
||||
s_get_network_members_with_capabilities: client.prepare_typed(GET_NETWORK_MEMBERS_WITH_CAPABILITIES_SQL, &[Type::TEXT]).await?,
|
||||
client,
|
||||
connection_task: runtime.spawn(async move {
|
||||
if let Err(e) = connection.await {
|
||||
|
@ -144,12 +139,7 @@ pub struct PostgresDatabase {
|
|||
}
|
||||
|
||||
impl PostgresDatabase {
|
||||
pub async fn new(
|
||||
runtime: Handle,
|
||||
postgres_path: String,
|
||||
num_connections: usize,
|
||||
local_identity: Valid<Identity>,
|
||||
) -> Result<Arc<Self>, Error> {
|
||||
pub async fn new(runtime: Handle, postgres_path: String, num_connections: usize, local_identity: Valid<Identity>) -> Result<Arc<Self>, Error> {
|
||||
assert!(num_connections > 0);
|
||||
let (sender, _) = channel(4096);
|
||||
let mut connections = Vec::with_capacity(num_connections);
|
||||
|
@ -193,7 +183,7 @@ impl VL1DataStorage for PostgresDatabase {
|
|||
Some(self.local_identity.clone())
|
||||
}
|
||||
|
||||
fn save_node_identity(&self, id: &Valid<Identity>) -> bool {
|
||||
fn save_node_identity(&self, _id: &Valid<Identity>) -> bool {
|
||||
panic!("local identity saving not supported by PostgresDatabase")
|
||||
}
|
||||
}
|
||||
|
@ -217,11 +207,7 @@ impl Database for PostgresDatabase {
|
|||
let c = self.get_connection().await?;
|
||||
let network_id_string = id.to_string();
|
||||
if let Some(r) = c.client.query_opt(&c.s_get_network, &[&network_id_string]).await? {
|
||||
if let Ok(with_caps) = c
|
||||
.client
|
||||
.query(&c.s_get_network_members_with_capabilities, &[&network_id_string])
|
||||
.await
|
||||
{
|
||||
if let Ok(with_caps) = c.client.query(&c.s_get_network_members_with_capabilities, &[&network_id_string]).await {
|
||||
(r, with_caps)
|
||||
} else {
|
||||
(r, Vec::new())
|
||||
|
@ -341,10 +327,7 @@ impl Database for PostgresDatabase {
|
|||
if let Ok(member_id) = Address::from_str(wc.get(0)) {
|
||||
if let Ok(cap_ids) = serde_json::from_str::<Vec<u32>>(wc.get(1)) {
|
||||
for cap_id in cap_ids.iter() {
|
||||
members_by_cap
|
||||
.entry(*cap_id)
|
||||
.or_insert_with(|| Vec::with_capacity(4))
|
||||
.push(member_id);
|
||||
members_by_cap.entry(*cap_id).or_insert_with(|| Vec::with_capacity(4)).push(member_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -389,7 +372,7 @@ impl Database for PostgresDatabase {
|
|||
}))
|
||||
}
|
||||
|
||||
async fn save_network(&self, obj: Network, generate_change_notification: bool) -> Result<(), Error> {
|
||||
async fn save_network(&self, _obj: Network, _generate_change_notification: bool) -> Result<(), Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
|
@ -405,11 +388,11 @@ impl Database for PostgresDatabase {
|
|||
Ok(r)
|
||||
}
|
||||
|
||||
async fn get_member(&self, network_id: NetworkId, node_id: Address) -> Result<Option<Member>, Error> {
|
||||
async fn get_member(&self, _network_id: NetworkId, _node_id: Address) -> Result<Option<Member>, Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn save_member(&self, obj: Member, generate_change_notification: bool) -> Result<(), Error> {
|
||||
async fn save_member(&self, _obj: Member, _generate_change_notification: bool) -> Result<(), Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
|
@ -443,7 +426,7 @@ impl Database for PostgresDatabase {
|
|||
return Ok(false);
|
||||
}
|
||||
|
||||
async fn log_request(&self, obj: RequestLogItem) -> Result<(), Error> {
|
||||
async fn log_request(&self, _obj: RequestLogItem) -> Result<(), Error> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,34 +1,38 @@
|
|||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use std::time::Duration;
|
||||
|
||||
use zerotier_crypto::mimcvdf;
|
||||
use zerotier_crypto::p384::*;
|
||||
use zerotier_crypto::random;
|
||||
use zerotier_crypto::x25519::*;
|
||||
|
||||
pub fn criterion_benchmark(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("cryptography");
|
||||
|
||||
let mut input = 1;
|
||||
let mut proof = 0;
|
||||
group.bench_function("mimcvdf::delay(1000)", |b| {
|
||||
b.iter(|| {
|
||||
input += 1;
|
||||
proof = mimcvdf::delay(input, 1000);
|
||||
})
|
||||
});
|
||||
group.bench_function("mimcvdf::verify(1000)", |b| {
|
||||
b.iter(|| {
|
||||
assert!(mimcvdf::verify(proof, input, 1000));
|
||||
})
|
||||
});
|
||||
|
||||
let p384_a = P384KeyPair::generate();
|
||||
let p384_b = P384KeyPair::generate();
|
||||
|
||||
//let kyber_a = pqc_kyber::keypair(&mut random::SecureRandom::default());
|
||||
//let kyber_encap = pqc_kyber::encapsulate(&kyber_a.public, &mut random::SecureRandom::default()).unwrap();
|
||||
|
||||
let x25519_a = X25519KeyPair::generate();
|
||||
let x25519_b = X25519KeyPair::generate();
|
||||
let x25519_b_pub = x25519_b.public_bytes();
|
||||
|
||||
let mut group = c.benchmark_group("cryptography");
|
||||
group.measurement_time(Duration::new(10, 0));
|
||||
|
||||
group.bench_function("ecdhp384", |b| {
|
||||
b.iter(|| p384_a.agree(p384_b.public_key()).expect("ecdhp384 failed"))
|
||||
});
|
||||
group.bench_function("ecdhp384", |b| b.iter(|| p384_a.agree(p384_b.public_key()).expect("ecdhp384 failed")));
|
||||
group.bench_function("ecdhx25519", |b| b.iter(|| x25519_a.agree(&x25519_b_pub)));
|
||||
//group.bench_function("kyber_encapsulate", |b| {
|
||||
// b.iter(|| pqc_kyber::encapsulate(&kyber_a.public, &mut random::SecureRandom::default()).expect("kyber encapsulate failed"))
|
||||
//});
|
||||
//group.bench_function("kyber_decapsulate", |b| {
|
||||
// b.iter(|| pqc_kyber::decapsulate(&kyber_encap.0, &kyber_a.secret).expect("kyber decapsulate failed"))
|
||||
//});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ mod fruit_flavored {
|
|||
data_out_len: usize,
|
||||
data_out_written: *mut usize,
|
||||
) -> i32;
|
||||
//fn CCCryptorReset(cryptor_ref: *mut c_void, iv: *const c_void) -> i32;
|
||||
fn CCCryptorReset(cryptor_ref: *mut c_void, iv: *const c_void) -> i32;
|
||||
fn CCCryptorRelease(cryptor_ref: *mut c_void) -> i32;
|
||||
fn CCCryptorGCMSetIV(cryptor_ref: *mut c_void, iv: *const c_void, iv_len: usize) -> i32;
|
||||
fn CCCryptorGCMAddAAD(cryptor_ref: *mut c_void, aad: *const c_void, len: usize) -> i32;
|
||||
|
@ -135,14 +135,7 @@ mod fruit_flavored {
|
|||
assert_eq!(data.len(), 16);
|
||||
unsafe {
|
||||
let mut data_out_written = 0;
|
||||
CCCryptorUpdate(
|
||||
self.0,
|
||||
data.as_ptr().cast(),
|
||||
16,
|
||||
data.as_mut_ptr().cast(),
|
||||
16,
|
||||
&mut data_out_written,
|
||||
);
|
||||
CCCryptorUpdate(self.0, data.as_ptr().cast(), 16, data.as_mut_ptr().cast(), 16, &mut data_out_written);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -168,14 +161,7 @@ mod fruit_flavored {
|
|||
assert_eq!(data.len(), 16);
|
||||
unsafe {
|
||||
let mut data_out_written = 0;
|
||||
CCCryptorUpdate(
|
||||
self.1,
|
||||
data.as_ptr().cast(),
|
||||
16,
|
||||
data.as_mut_ptr().cast(),
|
||||
16,
|
||||
&mut data_out_written,
|
||||
);
|
||||
CCCryptorUpdate(self.1, data.as_ptr().cast(), 16, data.as_mut_ptr().cast(), 16, &mut data_out_written);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -183,6 +169,101 @@ mod fruit_flavored {
|
|||
unsafe impl Send for Aes {}
|
||||
unsafe impl Sync for Aes {}
|
||||
|
||||
pub struct AesCtr(*mut c_void);
|
||||
|
||||
impl Drop for AesCtr {
|
||||
#[inline(always)]
|
||||
fn drop(&mut self) {
|
||||
unsafe { CCCryptorRelease(self.0) };
|
||||
}
|
||||
}
|
||||
|
||||
impl AesCtr {
|
||||
/// Construct a new AES-CTR cipher.
|
||||
/// Key must be 16, 24, or 32 bytes in length or a panic will occur.
|
||||
pub fn new(k: &[u8]) -> Self {
|
||||
if k.len() != 32 && k.len() != 24 && k.len() != 16 {
|
||||
panic!("AES supports 128, 192, or 256 bits keys");
|
||||
}
|
||||
unsafe {
|
||||
let mut ptr: *mut c_void = null_mut();
|
||||
let result = CCCryptorCreateWithMode(
|
||||
kCCEncrypt,
|
||||
kCCModeCTR,
|
||||
kCCAlgorithmAES,
|
||||
0,
|
||||
[0_u64; 2].as_ptr().cast(),
|
||||
k.as_ptr().cast(),
|
||||
k.len(),
|
||||
null(),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
&mut ptr,
|
||||
);
|
||||
if result != 0 {
|
||||
panic!("CCCryptorCreateWithMode for CTR mode returned {}", result);
|
||||
}
|
||||
AesCtr(ptr)
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize AES-CTR for encryption or decryption with the given IV.
|
||||
/// If it's already been used, this also resets the cipher. There is no separate reset.
|
||||
pub fn reset_set_iv(&mut self, iv: &[u8]) {
|
||||
unsafe {
|
||||
if iv.len() == 16 {
|
||||
if CCCryptorReset(self.0, iv.as_ptr().cast()) != 0 {
|
||||
panic!("CCCryptorReset for CTR mode failed (old MacOS bug)");
|
||||
}
|
||||
} else if iv.len() < 16 {
|
||||
let mut iv2 = [0_u8; 16];
|
||||
iv2[0..iv.len()].copy_from_slice(iv);
|
||||
if CCCryptorReset(self.0, iv2.as_ptr().cast()) != 0 {
|
||||
panic!("CCCryptorReset for CTR mode failed (old MacOS bug)");
|
||||
}
|
||||
} else {
|
||||
panic!("CTR IV must be less than or equal to 16 bytes in length");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Encrypt or decrypt (same operation with CTR mode)
|
||||
#[inline(always)]
|
||||
pub fn crypt(&mut self, input: &[u8], output: &mut [u8]) {
|
||||
unsafe {
|
||||
assert!(output.len() >= input.len());
|
||||
let mut data_out_written: usize = 0;
|
||||
CCCryptorUpdate(
|
||||
self.0,
|
||||
input.as_ptr().cast(),
|
||||
input.len(),
|
||||
output.as_mut_ptr().cast(),
|
||||
output.len(),
|
||||
&mut data_out_written,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Encrypt or decrypt in place (same operation with CTR mode)
|
||||
#[inline(always)]
|
||||
pub fn crypt_in_place(&mut self, data: &mut [u8]) {
|
||||
unsafe {
|
||||
let mut data_out_written: usize = 0;
|
||||
CCCryptorUpdate(
|
||||
self.0,
|
||||
data.as_ptr().cast(),
|
||||
data.len(),
|
||||
data.as_mut_ptr().cast(),
|
||||
data.len(),
|
||||
&mut data_out_written,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for AesCtr {}
|
||||
|
||||
pub struct AesGcm(*mut c_void, bool);
|
||||
|
||||
impl Drop for AesGcm {
|
||||
|
@ -262,15 +343,9 @@ mod fruit_flavored {
|
|||
pub fn crypt_in_place(&mut self, data: &mut [u8]) {
|
||||
unsafe {
|
||||
if self.1 {
|
||||
assert_eq!(
|
||||
CCCryptorGCMEncrypt(self.0, data.as_ptr().cast(), data.len(), data.as_mut_ptr().cast()),
|
||||
0
|
||||
);
|
||||
assert_eq!(CCCryptorGCMEncrypt(self.0, data.as_ptr().cast(), data.len(), data.as_mut_ptr().cast()), 0);
|
||||
} else {
|
||||
assert_eq!(
|
||||
CCCryptorGCMDecrypt(self.0, data.as_ptr().cast(), data.len(), data.as_mut_ptr().cast()),
|
||||
0
|
||||
);
|
||||
assert_eq!(CCCryptorGCMDecrypt(self.0, data.as_ptr().cast(), data.len(), data.as_mut_ptr().cast()), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -307,6 +382,17 @@ mod openssl_aes {
|
|||
use std::cell::UnsafeCell;
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
fn aes_ctr_by_key_size(ks: usize) -> Cipher {
|
||||
match ks {
|
||||
16 => Cipher::aes_128_ctr(),
|
||||
24 => Cipher::aes_192_ctr(),
|
||||
32 => Cipher::aes_256_ctr(),
|
||||
_ => {
|
||||
panic!("AES supports 128, 192, or 256 bits keys");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn aes_gcm_by_key_size(ks: usize) -> Cipher {
|
||||
match ks {
|
||||
16 => Cipher::aes_128_gcm(),
|
||||
|
@ -390,6 +476,53 @@ mod openssl_aes {
|
|||
unsafe impl Send for Aes {}
|
||||
unsafe impl Sync for Aes {}
|
||||
|
||||
pub struct AesCtr(Secret<32>, usize, Option<Crypter>);
|
||||
|
||||
impl AesCtr {
|
||||
/// Construct a new AES-CTR cipher.
|
||||
/// Key must be 16, 24, or 32 bytes in length or a panic will occur.
|
||||
#[inline(always)]
|
||||
pub fn new(k: &[u8]) -> Self {
|
||||
let mut s: Secret<32> = Secret::default();
|
||||
match k.len() {
|
||||
16 | 24 | 32 => {
|
||||
s.0[..k.len()].copy_from_slice(k);
|
||||
Self(s, k.len(), None)
|
||||
}
|
||||
_ => {
|
||||
panic!("AES supports 128, 192, or 256 bits keys");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize AES-CTR for encryption or decryption with the given IV.
|
||||
/// If it's already been used, this also resets the cipher. There is no separate reset.
|
||||
#[inline(always)]
|
||||
pub fn reset_set_iv(&mut self, iv: &[u8]) {
|
||||
let mut c = Crypter::new(aes_ctr_by_key_size(self.1), Mode::Encrypt, &self.0 .0[..self.1], Some(iv)).unwrap();
|
||||
c.pad(false);
|
||||
let _ = self.2.replace(c);
|
||||
}
|
||||
|
||||
/// Encrypt or decrypt (same operation with CTR mode)
|
||||
#[inline(always)]
|
||||
pub fn crypt(&mut self, input: &[u8], output: &mut [u8]) {
|
||||
let _ = self.2.as_mut().unwrap().update(input, output);
|
||||
}
|
||||
|
||||
/// Encrypt or decrypt in place (same operation with CTR mode)
|
||||
#[inline(always)]
|
||||
pub fn crypt_in_place(&mut self, data: &mut [u8]) {
|
||||
let _ = self
|
||||
.2
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.update(unsafe { &*std::slice::from_raw_parts(data.as_ptr(), data.len()) }, data);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for AesCtr {}
|
||||
|
||||
pub struct AesGcm(Secret<32>, usize, CipherCtx, bool);
|
||||
|
||||
impl AesGcm {
|
||||
|
@ -479,10 +612,10 @@ mod openssl_aes {
|
|||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use fruit_flavored::{Aes, AesGcm};
|
||||
pub use fruit_flavored::{Aes, AesCtr, AesGcm};
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub use openssl_aes::{Aes, AesGcm};
|
||||
pub use openssl_aes::{Aes, AesCtr, AesGcm};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
@ -178,15 +178,9 @@ impl AesGmacSiv {
|
|||
self.tmp.copy_from_slice(&tag_tmp[0..16]);
|
||||
|
||||
self.tmp[12] &= 0x7f;
|
||||
let _ = self.ctr.replace(
|
||||
Crypter::new(
|
||||
aes_ctr_by_key_size(self.k1.len()),
|
||||
Mode::Encrypt,
|
||||
self.k1.as_slice(),
|
||||
Some(&self.tmp),
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
let _ = self
|
||||
.ctr
|
||||
.replace(Crypter::new(aes_ctr_by_key_size(self.k1.len()), Mode::Encrypt, self.k1.as_slice(), Some(&self.tmp)).unwrap());
|
||||
}
|
||||
|
||||
/// Feed plaintext for second pass and write ciphertext to supplied buffer.
|
||||
|
@ -219,15 +213,9 @@ impl AesGmacSiv {
|
|||
pub fn decrypt_init(&mut self, tag: &[u8]) {
|
||||
self.tmp.copy_from_slice(tag);
|
||||
self.tmp[12] &= 0x7f;
|
||||
let _ = self.ctr.replace(
|
||||
Crypter::new(
|
||||
aes_ctr_by_key_size(self.k1.len()),
|
||||
Mode::Decrypt,
|
||||
self.k1.as_slice(),
|
||||
Some(&self.tmp),
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
let _ = self
|
||||
.ctr
|
||||
.replace(Crypter::new(aes_ctr_by_key_size(self.k1.len()), Mode::Decrypt, self.k1.as_slice(), Some(&self.tmp)).unwrap());
|
||||
|
||||
let mut tag_tmp = [0_u8; 32];
|
||||
let mut ecb = Crypter::new(aes_ecb_by_key_size(self.k1.len()), Mode::Decrypt, self.k1.as_slice(), None).unwrap();
|
||||
|
|
|
@ -7,6 +7,8 @@ use std::ptr::null;
|
|||
|
||||
pub const SHA512_HASH_SIZE: usize = 64;
|
||||
pub const SHA384_HASH_SIZE: usize = 48;
|
||||
pub const HMAC_SHA512_SIZE: usize = 64;
|
||||
pub const HMAC_SHA384_SIZE: usize = 48;
|
||||
|
||||
pub struct SHA512(Option<openssl::sha::Sha512>);
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
pub mod aes;
|
||||
pub mod aes_gmac_siv;
|
||||
pub mod hash;
|
||||
pub mod mimcvdf;
|
||||
pub mod p384;
|
||||
pub mod poly1305;
|
||||
pub mod random;
|
||||
|
@ -27,3 +28,13 @@ pub fn secure_eq<A: AsRef<[u8]> + ?Sized, B: AsRef<[u8]> + ?Sized>(a: &A, b: &B)
|
|||
false
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
fn OPENSSL_cleanse(ptr: *mut std::ffi::c_void, len: usize);
|
||||
}
|
||||
|
||||
/// Destroy the contents of some memory
|
||||
#[inline(always)]
|
||||
pub fn burn(b: &mut [u8]) {
|
||||
unsafe { OPENSSL_cleanse(b.as_mut_ptr().cast(), b.len()) };
|
||||
}
|
||||
|
|
142
crypto/src/mimcvdf.rs
Normal file
142
crypto/src/mimcvdf.rs
Normal file
|
@ -0,0 +1,142 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* (c) ZeroTier, Inc.
|
||||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
/*
|
||||
* MIMC is a cipher originally designed for use with STARK and SNARK proofs. It's based
|
||||
* on modular multiplication and exponentiation instead of the usual bit twiddling or ARX
|
||||
* operations that underpin more common hash algorithms.
|
||||
*
|
||||
* It's useful as a verifiable delay function because it can be computed in both directions with
|
||||
* one direction taking orders of magnitude longer than the other. The "backward" direction is
|
||||
* used as the delay function as it requires modular exponentiation which is inherently more
|
||||
* compute intensive. The "forward" direction simply requires modular cubing which is two modular
|
||||
* multiplications and is much faster.
|
||||
*
|
||||
* It's a nice VDF because it's incredibly simple with a tiny code footprint. Most other VDFs
|
||||
* involve RSA group operations or zero knowledge proofs.
|
||||
*
|
||||
* This is used for anti-DOS and anti-spamming delay functions. It's not used for anything
|
||||
* really "cryptographically hard," and if it were broken cryptographically it would still be
|
||||
* useful as a VDF as long as the break didn't yield a significantly faster way of computing a
|
||||
* delay proof than the straightforward iterative way implemented here.
|
||||
*
|
||||
* Here are two references on MIMC with the first being the original paper and the second being
|
||||
* a blog post describing its use as a VDF.
|
||||
*
|
||||
* https://eprint.iacr.org/2016/492.pdf
|
||||
* https://vitalik.ca/general/2018/07/21/starks_part_3.html
|
||||
*/
|
||||
|
||||
// p = 2^127 - 39, the largest 127-bit prime of the form 6k + 5
|
||||
const PRIME: u128 = 170141183460469231731687303715884105689;
|
||||
|
||||
// (2p - 1) / 3
|
||||
const PRIME_2P_MINUS_1_DIV_3: u128 = 113427455640312821154458202477256070459;
|
||||
|
||||
// Randomly generated round constants, each modulo PRIME.
|
||||
const K_COUNT_MASK: usize = 31;
|
||||
const K: [u128; 32] = [
|
||||
0x1fdd07a761b611bb1ab9419a70599a7c,
|
||||
0x23056b05d5c6b925e333d7418047650a,
|
||||
0x77a638f9b437a307f8866fbd2672c705,
|
||||
0x60213dab83bab91d1c310bd87e9da332,
|
||||
0xf56bc883301ab373179e46b098b7a7,
|
||||
0x7914a0dbd2f971344173b350c28a838,
|
||||
0x44bb64af5e446e6ebdc068d10d318f26,
|
||||
0x1bca1921fd328bb725ae0cbcbc20a263,
|
||||
0xafa963242f5216a7da1cd5328b23659,
|
||||
0x7fe17c43782b883a63ee0a790e0b2b77,
|
||||
0x23bb62abf728bf453200ee528f902c33,
|
||||
0x75ec0c055be14955db6878567e3c0465,
|
||||
0x7902bb57876e0b08b4de02a66755e5d7,
|
||||
0xe5d7094f37b615f5a1e1594b0390de8,
|
||||
0x12d4ddee90653a26f5de63ff4651f2d,
|
||||
0xce4a15bc35633b5ed8bcae2c93d739c,
|
||||
0x23f25b935e52df87255db8c608ef9ab4,
|
||||
0x611a08d7464fb984c98104d77f1609a7,
|
||||
0x7aa825876a7f6acde5efa57992da9c43,
|
||||
0x2be9686f630fa28a0a0e1081a59755b4,
|
||||
0x50060dac9ac4656ba3f8ee7592f4e28a,
|
||||
0x4113abff6f5bb303eac2ca809d4d529d,
|
||||
0x2af9d01d4e753feb5834c14ca0543397,
|
||||
0x73c2d764691ced2b823dda887e22ae85,
|
||||
0x5b53dcd4750ff888dca2497cec4dacb7,
|
||||
0x5d8984a52c2d8f3cc9bcf61ef29f8a1,
|
||||
0x588d8cc99533d649aabb5f0f552140e,
|
||||
0x4dae04985fde8c8464ba08aaa7d8761e,
|
||||
0x53f0c4740b8c3bda3fc05109b9a2b71,
|
||||
0x3e918c88a6795e3bf840e0b74d91b9d7,
|
||||
0x1dbcb30d724f11200aebb1dff87def91,
|
||||
0x6086b0af0e1e68558170239d23be9780,
|
||||
];
|
||||
|
||||
fn mulmod<const M: u128>(mut a: u128, mut b: u128) -> u128 {
|
||||
let mut res: u128 = 0;
|
||||
a %= M;
|
||||
loop {
|
||||
if (b & 1) != 0 {
|
||||
res = res.wrapping_add(a) % M;
|
||||
}
|
||||
b = b.wrapping_shr(1);
|
||||
if b != 0 {
|
||||
a = a.wrapping_shl(1) % M;
|
||||
} else {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn powmod<const M: u128>(mut base: u128, mut exp: u128) -> u128 {
|
||||
let mut res: u128 = 1;
|
||||
loop {
|
||||
if (exp & 1) != 0 {
|
||||
res = mulmod::<M>(base, res);
|
||||
}
|
||||
exp = exp.wrapping_shr(1);
|
||||
if exp != 0 {
|
||||
base = mulmod::<M>(base, base);
|
||||
} else {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute MIMC for the given number of iterations and return a proof that can be checked much more quickly.
|
||||
pub fn delay(mut input: u128, rounds: usize) -> u128 {
|
||||
debug_assert!(rounds > 0);
|
||||
input %= PRIME;
|
||||
for r in 1..(rounds + 1) {
|
||||
input = powmod::<PRIME>(input ^ K[(rounds - r) & K_COUNT_MASK], PRIME_2P_MINUS_1_DIV_3);
|
||||
}
|
||||
input
|
||||
}
|
||||
|
||||
/// Quickly verify the result of delay() given the returned proof, original input, and original number of rounds.
|
||||
pub fn verify(mut proof: u128, original_input: u128, rounds: usize) -> bool {
|
||||
debug_assert!(rounds > 0);
|
||||
for r in 0..rounds {
|
||||
proof = mulmod::<PRIME>(proof, mulmod::<PRIME>(proof, proof)) ^ K[r & K_COUNT_MASK];
|
||||
}
|
||||
proof == (original_input % PRIME)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn delay_and_verify() {
|
||||
for i in 1..5 {
|
||||
let input = (crate::random::xorshift64_random() as u128).wrapping_mul(crate::random::xorshift64_random() as u128);
|
||||
let proof = delay(input, i * 3);
|
||||
//println!("{}", proof);
|
||||
assert!(verify(proof, input, i * 3));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -55,9 +55,7 @@ mod openssl_based {
|
|||
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 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 {
|
||||
|
@ -599,17 +597,12 @@ mod builtin {
|
|||
let mut i: uint = 0; /* p = c0 */
|
||||
vli_clear(l_tmp.as_mut_ptr());
|
||||
vli_clear(l_tmp.as_mut_ptr().offset((48 as libc::c_int / 8 as libc::c_int) as isize));
|
||||
omega_mult(
|
||||
l_tmp.as_mut_ptr(),
|
||||
p_product.offset((48 as libc::c_int / 8 as libc::c_int) as isize),
|
||||
);
|
||||
omega_mult(l_tmp.as_mut_ptr(), p_product.offset((48 as libc::c_int / 8 as libc::c_int) as isize));
|
||||
vli_clear(p_product.offset((48 as libc::c_int / 8 as libc::c_int) as isize));
|
||||
/* (c1, c0) = c0 + w * c1 */
|
||||
i = 0 as libc::c_int as uint;
|
||||
while i < (48 as libc::c_int / 8 as libc::c_int + 3 as libc::c_int) as libc::c_uint {
|
||||
let mut l_sum: uint64_t = (*p_product.offset(i as isize))
|
||||
.wrapping_add(l_tmp[i as usize])
|
||||
.wrapping_add(l_carry);
|
||||
let mut l_sum: uint64_t = (*p_product.offset(i as isize)).wrapping_add(l_tmp[i as usize]).wrapping_add(l_carry);
|
||||
if l_sum != *p_product.offset(i as isize) {
|
||||
l_carry = (l_sum < *p_product.offset(i as isize)) as libc::c_int as uint64_t
|
||||
}
|
||||
|
@ -855,12 +848,7 @@ mod builtin {
|
|||
vli_set(X1, t7.as_mut_ptr());
|
||||
}
|
||||
|
||||
unsafe fn EccPoint_mult(
|
||||
mut p_result: *mut EccPoint,
|
||||
mut p_point: *mut EccPoint,
|
||||
mut p_scalar: *mut uint64_t,
|
||||
mut p_initialZ: *mut uint64_t,
|
||||
) {
|
||||
unsafe fn EccPoint_mult(mut p_result: *mut EccPoint, mut p_point: *mut EccPoint, mut p_scalar: *mut uint64_t, mut p_initialZ: *mut uint64_t) {
|
||||
/* R0 and R1 */
|
||||
let mut Rx: [[uint64_t; 6]; 2] = std::mem::MaybeUninit::uninit().assume_init();
|
||||
let mut Ry: [[uint64_t; 6]; 2] = std::mem::MaybeUninit::uninit().assume_init();
|
||||
|
@ -1039,20 +1027,12 @@ mod builtin {
|
|||
}
|
||||
}
|
||||
ecc_native2bytes(p_privateKey, l_private.as_mut_ptr() as *const uint64_t);
|
||||
ecc_native2bytes(
|
||||
p_publicKey.offset(1 as libc::c_int as isize),
|
||||
l_public.x.as_mut_ptr() as *const uint64_t,
|
||||
);
|
||||
*p_publicKey.offset(0 as libc::c_int as isize) = (2 as libc::c_int as libc::c_ulong)
|
||||
.wrapping_add(l_public.y[0 as libc::c_int as usize] & 0x1 as libc::c_int as libc::c_ulong)
|
||||
as uint8_t;
|
||||
ecc_native2bytes(p_publicKey.offset(1 as libc::c_int as isize), l_public.x.as_mut_ptr() as *const uint64_t);
|
||||
*p_publicKey.offset(0 as libc::c_int as isize) =
|
||||
(2 as libc::c_int as libc::c_ulong).wrapping_add(l_public.y[0 as libc::c_int as usize] & 0x1 as libc::c_int as libc::c_ulong) as uint8_t;
|
||||
return 1 as libc::c_int;
|
||||
}
|
||||
pub unsafe fn ecdh_shared_secret(
|
||||
mut p_publicKey: *const uint8_t,
|
||||
mut p_privateKey: *const uint8_t,
|
||||
mut p_secret: *mut uint8_t,
|
||||
) -> libc::c_int {
|
||||
pub unsafe fn ecdh_shared_secret(mut p_publicKey: *const uint8_t, mut p_privateKey: *const uint8_t, mut p_secret: *mut uint8_t) -> libc::c_int {
|
||||
let mut l_public: EccPoint = std::mem::MaybeUninit::uninit().assume_init();
|
||||
let mut l_private: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init();
|
||||
let mut l_random: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init();
|
||||
|
@ -1079,8 +1059,7 @@ mod builtin {
|
|||
vli_mult(l_product.as_mut_ptr(), p_left, p_right);
|
||||
l_productBits = vli_numBits(l_product.as_mut_ptr().offset((48 as libc::c_int / 8 as libc::c_int) as isize));
|
||||
if l_productBits != 0 {
|
||||
l_productBits = (l_productBits as libc::c_uint)
|
||||
.wrapping_add((48 as libc::c_int / 8 as libc::c_int * 64 as libc::c_int) as libc::c_uint)
|
||||
l_productBits = (l_productBits as libc::c_uint).wrapping_add((48 as libc::c_int / 8 as libc::c_int * 64 as libc::c_int) as libc::c_uint)
|
||||
as uint as uint
|
||||
} else {
|
||||
l_productBits = vli_numBits(l_product.as_mut_ptr())
|
||||
|
@ -1094,12 +1073,8 @@ mod builtin {
|
|||
power of two possible while still resulting in a number less than p_left. */
|
||||
vli_clear(l_modMultiple.as_mut_ptr());
|
||||
vli_clear(l_modMultiple.as_mut_ptr().offset((48 as libc::c_int / 8 as libc::c_int) as isize));
|
||||
l_digitShift = l_productBits
|
||||
.wrapping_sub(l_modBits)
|
||||
.wrapping_div(64 as libc::c_int as libc::c_uint);
|
||||
l_bitShift = l_productBits
|
||||
.wrapping_sub(l_modBits)
|
||||
.wrapping_rem(64 as libc::c_int as libc::c_uint);
|
||||
l_digitShift = l_productBits.wrapping_sub(l_modBits).wrapping_div(64 as libc::c_int as libc::c_uint);
|
||||
l_bitShift = l_productBits.wrapping_sub(l_modBits).wrapping_rem(64 as libc::c_int as libc::c_uint);
|
||||
if l_bitShift != 0 {
|
||||
l_modMultiple[l_digitShift.wrapping_add((48 as libc::c_int / 8 as libc::c_int) as libc::c_uint) as usize] =
|
||||
vli_lshift(l_modMultiple.as_mut_ptr().offset(l_digitShift as isize), p_mod, l_bitShift)
|
||||
|
@ -1186,11 +1161,7 @@ mod builtin {
|
|||
ecc_native2bytes(p_signature.offset(48 as libc::c_int as isize), l_s.as_mut_ptr() as *const uint64_t);
|
||||
return 1 as libc::c_int;
|
||||
}
|
||||
pub unsafe fn ecdsa_verify(
|
||||
mut p_publicKey: *const uint8_t,
|
||||
mut p_hash: *const uint8_t,
|
||||
mut p_signature: *const uint8_t,
|
||||
) -> libc::c_int {
|
||||
pub unsafe fn ecdsa_verify(mut p_publicKey: *const uint8_t, mut p_hash: *const uint8_t, mut p_signature: *const uint8_t) -> libc::c_int {
|
||||
let mut u1: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init();
|
||||
let mut u2: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init();
|
||||
let mut z: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init();
|
||||
|
@ -1210,8 +1181,7 @@ mod builtin {
|
|||
/* r, s must not be 0. */
|
||||
return 0 as libc::c_int;
|
||||
}
|
||||
if vli_cmp(curve_n.as_mut_ptr(), l_r.as_mut_ptr()) != 1 as libc::c_int
|
||||
|| vli_cmp(curve_n.as_mut_ptr(), l_s.as_mut_ptr()) != 1 as libc::c_int
|
||||
if vli_cmp(curve_n.as_mut_ptr(), l_r.as_mut_ptr()) != 1 as libc::c_int || vli_cmp(curve_n.as_mut_ptr(), l_s.as_mut_ptr()) != 1 as libc::c_int
|
||||
{
|
||||
/* r, s must be < n. */
|
||||
return 0 as libc::c_int;
|
||||
|
@ -1233,10 +1203,10 @@ mod builtin {
|
|||
/* Use Shamir's trick to calculate u1*G + u2*Q */
|
||||
let mut l_points: [*mut EccPoint; 4] = [0 as *mut EccPoint, &mut curve_G, &mut l_public, &mut l_sum]; /* Z = x2 - x1 */
|
||||
let mut l_numBits: uint = umax(vli_numBits(u1.as_mut_ptr()), vli_numBits(u2.as_mut_ptr())); /* Z = 1/Z */
|
||||
let mut l_point: *mut EccPoint = l_points[((vli_testBit(u1.as_mut_ptr(), l_numBits.wrapping_sub(1 as libc::c_int as libc::c_uint))
|
||||
!= 0) as libc::c_int
|
||||
| ((vli_testBit(u2.as_mut_ptr(), l_numBits.wrapping_sub(1 as libc::c_int as libc::c_uint)) != 0) as libc::c_int)
|
||||
<< 1 as libc::c_int) as usize];
|
||||
let mut l_point: *mut EccPoint = l_points[((vli_testBit(u1.as_mut_ptr(), l_numBits.wrapping_sub(1 as libc::c_int as libc::c_uint)) != 0)
|
||||
as libc::c_int
|
||||
| ((vli_testBit(u2.as_mut_ptr(), l_numBits.wrapping_sub(1 as libc::c_int as libc::c_uint)) != 0) as libc::c_int) << 1 as libc::c_int)
|
||||
as usize];
|
||||
vli_set(rx.as_mut_ptr(), (*l_point).x.as_mut_ptr());
|
||||
vli_set(ry.as_mut_ptr(), (*l_point).y.as_mut_ptr());
|
||||
vli_clear(z.as_mut_ptr());
|
||||
|
|
|
@ -20,12 +20,12 @@ mod tests {
|
|||
use crate::poly1305::*;
|
||||
|
||||
const TV0_INPUT: [u8; 32] = [
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
const TV0_KEY: [u8; 32] = [
|
||||
0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x33, 0x32, 0x2d, 0x62, 0x79, 0x74, 0x65, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x66, 0x6f,
|
||||
0x72, 0x20, 0x50, 0x6f, 0x6c, 0x79, 0x31, 0x33, 0x30, 0x35,
|
||||
0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x33, 0x32, 0x2d, 0x62, 0x79, 0x74, 0x65, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x66, 0x6f, 0x72,
|
||||
0x20, 0x50, 0x6f, 0x6c, 0x79, 0x31, 0x33, 0x30, 0x35,
|
||||
];
|
||||
const TV0_TAG: [u8; 16] = [
|
||||
0x49, 0xec, 0x78, 0x09, 0x0e, 0x48, 0x1e, 0xc6, 0xc2, 0x6b, 0x33, 0xb9, 0x1c, 0xcc, 0x03, 0x07,
|
||||
|
@ -33,8 +33,8 @@ mod tests {
|
|||
|
||||
const TV1_INPUT: [u8; 12] = [0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21];
|
||||
const TV1_KEY: [u8; 32] = [
|
||||
0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x33, 0x32, 0x2d, 0x62, 0x79, 0x74, 0x65, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x66, 0x6f,
|
||||
0x72, 0x20, 0x50, 0x6f, 0x6c, 0x79, 0x31, 0x33, 0x30, 0x35,
|
||||
0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x33, 0x32, 0x2d, 0x62, 0x79, 0x74, 0x65, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x66, 0x6f, 0x72,
|
||||
0x20, 0x50, 0x6f, 0x6c, 0x79, 0x31, 0x33, 0x30, 0x35,
|
||||
];
|
||||
const TV1_TAG: [u8; 16] = [
|
||||
0xa6, 0xf7, 0x45, 0x00, 0x8f, 0x81, 0xc9, 0x16, 0xa2, 0x0d, 0xcc, 0x74, 0xee, 0xf2, 0xb2, 0xf0,
|
||||
|
|
|
@ -242,14 +242,14 @@ mod tests {
|
|||
use crate::salsa::*;
|
||||
|
||||
const SALSA_20_TV0_KEY: [u8; 32] = [
|
||||
0x0f, 0x62, 0xb5, 0x08, 0x5b, 0xae, 0x01, 0x54, 0xa7, 0xfa, 0x4d, 0xa0, 0xf3, 0x46, 0x99, 0xec, 0x3f, 0x92, 0xe5, 0x38, 0x8b, 0xde,
|
||||
0x31, 0x84, 0xd7, 0x2a, 0x7d, 0xd0, 0x23, 0x76, 0xc9, 0x1c,
|
||||
0x0f, 0x62, 0xb5, 0x08, 0x5b, 0xae, 0x01, 0x54, 0xa7, 0xfa, 0x4d, 0xa0, 0xf3, 0x46, 0x99, 0xec, 0x3f, 0x92, 0xe5, 0x38, 0x8b, 0xde, 0x31,
|
||||
0x84, 0xd7, 0x2a, 0x7d, 0xd0, 0x23, 0x76, 0xc9, 0x1c,
|
||||
];
|
||||
const SALSA_20_TV0_IV: [u8; 8] = [0x28, 0x8f, 0xf6, 0x5d, 0xc4, 0x2b, 0x92, 0xf9];
|
||||
const SALSA_20_TV0_KS: [u8; 64] = [
|
||||
0x5e, 0x5e, 0x71, 0xf9, 0x01, 0x99, 0x34, 0x03, 0x04, 0xab, 0xb2, 0x2a, 0x37, 0xb6, 0x62, 0x5b, 0xf8, 0x83, 0xfb, 0x89, 0xce, 0x3b,
|
||||
0x21, 0xf5, 0x4a, 0x10, 0xb8, 0x10, 0x66, 0xef, 0x87, 0xda, 0x30, 0xb7, 0x76, 0x99, 0xaa, 0x73, 0x79, 0xda, 0x59, 0x5c, 0x77, 0xdd,
|
||||
0x59, 0x54, 0x2d, 0xa2, 0x08, 0xe5, 0x95, 0x4f, 0x89, 0xe4, 0x0e, 0xb7, 0xaa, 0x80, 0xa8, 0x4a, 0x61, 0x76, 0x66, 0x3f,
|
||||
0x5e, 0x5e, 0x71, 0xf9, 0x01, 0x99, 0x34, 0x03, 0x04, 0xab, 0xb2, 0x2a, 0x37, 0xb6, 0x62, 0x5b, 0xf8, 0x83, 0xfb, 0x89, 0xce, 0x3b, 0x21,
|
||||
0xf5, 0x4a, 0x10, 0xb8, 0x10, 0x66, 0xef, 0x87, 0xda, 0x30, 0xb7, 0x76, 0x99, 0xaa, 0x73, 0x79, 0xda, 0x59, 0x5c, 0x77, 0xdd, 0x59, 0x54,
|
||||
0x2d, 0xa2, 0x08, 0xe5, 0x95, 0x4f, 0x89, 0xe4, 0x0e, 0xb7, 0xaa, 0x80, 0xa8, 0x4a, 0x61, 0x76, 0x66, 0x3f,
|
||||
];
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -37,6 +37,11 @@ impl<const L: usize> Secret<L> {
|
|||
&self.0
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn as_bytes_mut(&mut self) -> &mut [u8; L] {
|
||||
&mut self.0
|
||||
}
|
||||
|
||||
/// Get the first N bytes of this secret as a fixed length array.
|
||||
#[inline(always)]
|
||||
pub fn first_n<const N: usize>(&self) -> &[u8; N] {
|
||||
|
|
|
@ -216,10 +216,7 @@ impl Marshalable for Endpoint {
|
|||
TYPE_NIL => Ok(Endpoint::Nil),
|
||||
TYPE_ZEROTIER => {
|
||||
let zt = Address::from_bytes_fixed(buf.read_bytes_fixed(cursor)?).ok_or(UnmarshalError::InvalidData)?;
|
||||
Ok(Endpoint::ZeroTier(
|
||||
zt,
|
||||
buf.read_bytes_fixed::<IDENTITY_FINGERPRINT_SIZE>(cursor)?.clone(),
|
||||
))
|
||||
Ok(Endpoint::ZeroTier(zt, buf.read_bytes_fixed::<IDENTITY_FINGERPRINT_SIZE>(cursor)?.clone()))
|
||||
}
|
||||
TYPE_ETHERNET => Ok(Endpoint::Ethernet(MAC::unmarshal(buf, cursor)?)),
|
||||
TYPE_WIFIDIRECT => Ok(Endpoint::WifiDirect(MAC::unmarshal(buf, cursor)?)),
|
||||
|
@ -230,9 +227,7 @@ impl Marshalable for Endpoint {
|
|||
TYPE_HTTP => Ok(Endpoint::Http(
|
||||
String::from_utf8_lossy(buf.read_bytes(buf.read_varint(cursor)? as usize, cursor)?).to_string(),
|
||||
)),
|
||||
TYPE_WEBRTC => Ok(Endpoint::WebRTC(
|
||||
buf.read_bytes(buf.read_varint(cursor)? as usize, cursor)?.to_vec(),
|
||||
)),
|
||||
TYPE_WEBRTC => Ok(Endpoint::WebRTC(buf.read_bytes(buf.read_varint(cursor)? as usize, cursor)?.to_vec())),
|
||||
TYPE_ZEROTIER_ENCAP => {
|
||||
let zt = Address::from_bytes_fixed(buf.read_bytes_fixed(cursor)?).ok_or(UnmarshalError::InvalidData)?;
|
||||
Ok(Endpoint::ZeroTierEncap(zt, buf.read_bytes_fixed(cursor)?.clone()))
|
||||
|
@ -361,10 +356,7 @@ impl FromStr for Endpoint {
|
|||
if endpoint_type == "zt" {
|
||||
return Ok(Endpoint::ZeroTier(Address::from_str(address)?, hash.as_slice().try_into().unwrap()));
|
||||
} else {
|
||||
return Ok(Endpoint::ZeroTierEncap(
|
||||
Address::from_str(address)?,
|
||||
hash.as_slice().try_into().unwrap(),
|
||||
));
|
||||
return Ok(Endpoint::ZeroTierEncap(Address::from_str(address)?, hash.as_slice().try_into().unwrap()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -587,11 +579,7 @@ mod tests {
|
|||
|
||||
let inet = crate::vl1::InetAddress::from_ip_port(&v, 1234);
|
||||
|
||||
for e in [
|
||||
Endpoint::Icmp(inet.clone()),
|
||||
Endpoint::IpTcp(inet.clone()),
|
||||
Endpoint::IpUdp(inet.clone()),
|
||||
] {
|
||||
for e in [Endpoint::Icmp(inet.clone()), Endpoint::IpTcp(inet.clone()), Endpoint::IpUdp(inet.clone())] {
|
||||
let mut buf = Buffer::<20>::new();
|
||||
|
||||
let res = e.marshal(&mut buf);
|
||||
|
|
|
@ -222,9 +222,7 @@ impl Identity {
|
|||
/// It would be possible to change this in the future, with care.
|
||||
pub fn upgrade(&mut self) -> Result<bool, InvalidParameterError> {
|
||||
if self.secret.is_none() {
|
||||
return Err(InvalidParameterError(
|
||||
"an identity can only be upgraded if it includes its private key",
|
||||
));
|
||||
return Err(InvalidParameterError("an identity can only be upgraded if it includes its private key"));
|
||||
}
|
||||
if self.p384.is_none() {
|
||||
let p384_ecdh = P384KeyPair::generate();
|
||||
|
@ -292,9 +290,8 @@ impl Identity {
|
|||
/// This is somewhat time consuming due to the memory-intensive work algorithm.
|
||||
pub fn validate(self) -> Option<Valid<Self>> {
|
||||
if let Some(p384) = self.p384.as_ref() {
|
||||
let mut self_sign_buf: Vec<u8> = Vec::with_capacity(
|
||||
ADDRESS_SIZE + 4 + C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE,
|
||||
);
|
||||
let mut self_sign_buf: Vec<u8> =
|
||||
Vec::with_capacity(ADDRESS_SIZE + 4 + C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE);
|
||||
let _ = self_sign_buf.write_all(&self.address.to_bytes());
|
||||
let _ = self_sign_buf.write_all(&self.x25519);
|
||||
let _ = self_sign_buf.write_all(&self.ed25519);
|
||||
|
@ -331,11 +328,7 @@ impl Identity {
|
|||
///
|
||||
/// This does NOT validate either identity. Ensure that validation has been performed.
|
||||
pub fn is_upgraded_from(&self, other: &Identity) -> bool {
|
||||
self.address == other.address
|
||||
&& self.x25519 == other.x25519
|
||||
&& self.ed25519 == other.ed25519
|
||||
&& self.p384.is_some()
|
||||
&& other.p384.is_none()
|
||||
self.address == other.address && self.x25519 == other.x25519 && self.ed25519 == other.ed25519 && self.p384.is_some() && other.p384.is_none()
|
||||
}
|
||||
|
||||
/// Perform ECDH key agreement, returning a shared secret or None on error.
|
||||
|
@ -493,13 +486,12 @@ impl Identity {
|
|||
s.push(':');
|
||||
}
|
||||
s.push_str(":2:"); // 2 == IDENTITY_ALGORITHM_EC_NIST_P384
|
||||
let p384_joined: [u8; P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE] =
|
||||
concat_arrays_4(
|
||||
p384.ecdh.as_bytes(),
|
||||
p384.ecdsa.as_bytes(),
|
||||
&p384.ecdsa_self_signature,
|
||||
&p384.ed25519_self_signature,
|
||||
);
|
||||
let p384_joined: [u8; P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE] = concat_arrays_4(
|
||||
p384.ecdh.as_bytes(),
|
||||
p384.ecdsa.as_bytes(),
|
||||
&p384.ecdsa_self_signature,
|
||||
&p384.ed25519_self_signature,
|
||||
);
|
||||
s.push_str(base64_encode_url_nopad(&p384_joined).as_str());
|
||||
if self.secret.is_some() && include_private {
|
||||
let secret = self.secret.as_ref().unwrap();
|
||||
|
@ -600,9 +592,7 @@ impl FromStr for Identity {
|
|||
if keys[0].len() != C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE {
|
||||
return Err(InvalidFormatError);
|
||||
}
|
||||
if !keys[2].is_empty()
|
||||
&& keys[2].len() != P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE
|
||||
{
|
||||
if !keys[2].is_empty() && keys[2].len() != P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE {
|
||||
return Err(InvalidFormatError);
|
||||
}
|
||||
if !keys[3].is_empty() && keys[3].len() != P384_SECRET_KEY_SIZE + P384_SECRET_KEY_SIZE {
|
||||
|
@ -632,8 +622,7 @@ impl FromStr for Identity {
|
|||
Some(IdentityP384Public {
|
||||
ecdh: ecdh.unwrap(),
|
||||
ecdsa: ecdsa.unwrap(),
|
||||
ecdsa_self_signature: keys[2].as_slice()
|
||||
[(P384_PUBLIC_KEY_SIZE * 2)..((P384_PUBLIC_KEY_SIZE * 2) + P384_ECDSA_SIGNATURE_SIZE)]
|
||||
ecdsa_self_signature: keys[2].as_slice()[(P384_PUBLIC_KEY_SIZE * 2)..((P384_PUBLIC_KEY_SIZE * 2) + P384_ECDSA_SIGNATURE_SIZE)]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
ed25519_self_signature: keys[2].as_slice()[((P384_PUBLIC_KEY_SIZE * 2) + P384_ECDSA_SIGNATURE_SIZE)..]
|
||||
|
@ -667,10 +656,8 @@ impl FromStr for Identity {
|
|||
} else {
|
||||
Some(IdentityP384Secret {
|
||||
ecdh: {
|
||||
let tmp = P384KeyPair::from_bytes(
|
||||
&keys[2].as_slice()[..P384_PUBLIC_KEY_SIZE],
|
||||
&keys[3].as_slice()[..P384_SECRET_KEY_SIZE],
|
||||
);
|
||||
let tmp =
|
||||
P384KeyPair::from_bytes(&keys[2].as_slice()[..P384_PUBLIC_KEY_SIZE], &keys[3].as_slice()[..P384_SECRET_KEY_SIZE]);
|
||||
if tmp.is_none() {
|
||||
return Err(InvalidFormatError);
|
||||
}
|
||||
|
@ -711,16 +698,8 @@ impl Marshalable for Identity {
|
|||
let x25519 = buf.read_bytes_fixed::<C25519_PUBLIC_KEY_SIZE>(cursor)?;
|
||||
let ed25519 = buf.read_bytes_fixed::<ED25519_PUBLIC_KEY_SIZE>(cursor)?;
|
||||
|
||||
let (
|
||||
mut ecdh,
|
||||
mut ecdsa,
|
||||
mut ecdsa_self_signature,
|
||||
mut ed25519_self_signature,
|
||||
mut x25519_s,
|
||||
mut ed25519_s,
|
||||
mut ecdh_s,
|
||||
mut ecdsa_s,
|
||||
) = (None, None, None, None, None, None, None, None);
|
||||
let (mut ecdh, mut ecdsa, mut ecdsa_self_signature, mut ed25519_self_signature, mut x25519_s, mut ed25519_s, mut ecdh_s, mut ecdsa_s) =
|
||||
(None, None, None, None, None, None, None, None);
|
||||
|
||||
if type_flags == 0 {
|
||||
const C25519_SECRETS_SIZE: u8 = (C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE) as u8;
|
||||
|
@ -736,9 +715,7 @@ impl Marshalable for Identity {
|
|||
_ => return Err(UnmarshalError::InvalidData),
|
||||
}
|
||||
} else {
|
||||
if (type_flags & (Self::ALGORITHM_X25519 | Self::FLAG_INCLUDES_SECRETS))
|
||||
== (Self::ALGORITHM_X25519 | Self::FLAG_INCLUDES_SECRETS)
|
||||
{
|
||||
if (type_flags & (Self::ALGORITHM_X25519 | Self::FLAG_INCLUDES_SECRETS)) == (Self::ALGORITHM_X25519 | Self::FLAG_INCLUDES_SECRETS) {
|
||||
x25519_s = Some(buf.read_bytes_fixed::<C25519_SECRET_KEY_SIZE>(cursor)?);
|
||||
ed25519_s = Some(buf.read_bytes_fixed::<ED25519_SECRET_KEY_SIZE>(cursor)?);
|
||||
}
|
||||
|
@ -772,17 +749,12 @@ impl Marshalable for Identity {
|
|||
secret: if let Some(x25519_s) = x25519_s {
|
||||
Some(IdentitySecret {
|
||||
x25519: X25519KeyPair::from_bytes(x25519, x25519_s).ok_or(UnmarshalError::InvalidData)?,
|
||||
ed25519: Ed25519KeyPair::from_bytes(ed25519, ed25519_s.ok_or(UnmarshalError::InvalidData)?)
|
||||
.ok_or(UnmarshalError::InvalidData)?,
|
||||
ed25519: Ed25519KeyPair::from_bytes(ed25519, ed25519_s.ok_or(UnmarshalError::InvalidData)?).ok_or(UnmarshalError::InvalidData)?,
|
||||
p384: if let Some(ecdh_s) = ecdh_s {
|
||||
Some(IdentityP384Secret {
|
||||
ecdh: P384KeyPair::from_bytes(ecdh.ok_or(UnmarshalError::InvalidData)?, ecdh_s)
|
||||
ecdh: P384KeyPair::from_bytes(ecdh.ok_or(UnmarshalError::InvalidData)?, ecdh_s).ok_or(UnmarshalError::InvalidData)?,
|
||||
ecdsa: P384KeyPair::from_bytes(ecdsa.ok_or(UnmarshalError::InvalidData)?, ecdsa_s.ok_or(UnmarshalError::InvalidData)?)
|
||||
.ok_or(UnmarshalError::InvalidData)?,
|
||||
ecdsa: P384KeyPair::from_bytes(
|
||||
ecdsa.ok_or(UnmarshalError::InvalidData)?,
|
||||
ecdsa_s.ok_or(UnmarshalError::InvalidData)?,
|
||||
)
|
||||
.ok_or(UnmarshalError::InvalidData)?,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
|
@ -810,9 +782,7 @@ impl Eq for Identity {}
|
|||
impl Ord for Identity {
|
||||
#[inline(always)]
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.address
|
||||
.cmp(&other.address)
|
||||
.then_with(|| self.fingerprint.cmp(&other.fingerprint))
|
||||
self.address.cmp(&other.address).then_with(|| self.fingerprint.cmp(&other.fingerprint))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -941,11 +911,7 @@ mod tests {
|
|||
let gen_unmarshaled = Identity::from_bytes(bytes.as_bytes()).unwrap();
|
||||
assert!(gen_unmarshaled.secret.is_some());
|
||||
if !gen_unmarshaled.eq(&gen) {
|
||||
println!(
|
||||
"{} != {}",
|
||||
hex::to_string(&gen_unmarshaled.fingerprint),
|
||||
hex::to_string(&gen.fingerprint)
|
||||
);
|
||||
println!("{} != {}", hex::to_string(&gen_unmarshaled.fingerprint), hex::to_string(&gen.fingerprint));
|
||||
}
|
||||
|
||||
assert!(Identity::from_str(string.as_str()).unwrap().secret.is_some());
|
||||
|
|
|
@ -892,11 +892,7 @@ impl Marshalable for InetAddress {
|
|||
AF_INET6 => {
|
||||
let b = buf.append_bytes_fixed_get_mut::<19>()?;
|
||||
b[0] = 6;
|
||||
copy_nonoverlapping(
|
||||
(&(self.sin6.sin6_addr) as *const in6_addr).cast::<u8>(),
|
||||
b.as_mut_ptr().offset(1),
|
||||
16,
|
||||
);
|
||||
copy_nonoverlapping((&(self.sin6.sin6_addr) as *const in6_addr).cast::<u8>(), b.as_mut_ptr().offset(1), 16);
|
||||
b[17] = *(&self.sin6.sin6_port as *const u16).cast::<u8>();
|
||||
b[18] = *(&self.sin6.sin6_port as *const u16).cast::<u8>().offset(1);
|
||||
}
|
||||
|
@ -913,10 +909,7 @@ impl Marshalable for InetAddress {
|
|||
Ok(InetAddress::from_ip_port(&b[0..4], u16::from_be_bytes(b[4..6].try_into().unwrap())))
|
||||
} else if t == 6 {
|
||||
let b: &[u8; 18] = buf.read_bytes_fixed(cursor)?;
|
||||
Ok(InetAddress::from_ip_port(
|
||||
&b[0..16],
|
||||
u16::from_be_bytes(b[16..18].try_into().unwrap()),
|
||||
))
|
||||
Ok(InetAddress::from_ip_port(&b[0..16], u16::from_be_bytes(b[16..18].try_into().unwrap())))
|
||||
} else {
|
||||
Ok(InetAddress::new())
|
||||
}
|
||||
|
|
|
@ -27,12 +27,7 @@ impl MAC {
|
|||
pub fn from_bytes(b: &[u8]) -> Option<MAC> {
|
||||
if b.len() >= 6 {
|
||||
NonZeroU64::new(
|
||||
(b[0] as u64) << 40
|
||||
| (b[1] as u64) << 32
|
||||
| (b[2] as u64) << 24
|
||||
| (b[3] as u64) << 16 as u64
|
||||
| (b[4] as u64) << 8
|
||||
| b[5] as u64,
|
||||
(b[0] as u64) << 40 | (b[1] as u64) << 32 | (b[2] as u64) << 24 | (b[3] as u64) << 16 as u64 | (b[4] as u64) << 8 | b[5] as u64,
|
||||
)
|
||||
.map(|i| MAC(i))
|
||||
} else {
|
||||
|
@ -94,10 +89,7 @@ impl Marshalable for MAC {
|
|||
impl ToString for MAC {
|
||||
fn to_string(&self) -> String {
|
||||
let b: [u8; 6] = self.to_bytes();
|
||||
format!(
|
||||
"{:0>2x}:{:0>2x}:{:0>2x}:{:0>2x}:{:0>2x}:{:0>2x}",
|
||||
b[0], b[1], b[2], b[3], b[4], b[5]
|
||||
)
|
||||
format!("{:0>2x}:{:0>2x}:{:0>2x}:{:0>2x}:{:0>2x}:{:0>2x}", b[0], b[1], b[2], b[3], b[4], b[5])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -388,14 +388,7 @@ impl Node {
|
|||
/// Get the root sets that this node trusts.
|
||||
#[inline]
|
||||
pub fn root_sets(&self) -> Vec<RootSet> {
|
||||
self.roots
|
||||
.read()
|
||||
.unwrap()
|
||||
.sets
|
||||
.values()
|
||||
.cloned()
|
||||
.map(|s| s.remove_typestate())
|
||||
.collect()
|
||||
self.roots.read().unwrap().sets.values().cloned().map(|s| s.remove_typestate()).collect()
|
||||
}
|
||||
|
||||
pub fn do_background_tasks<Application: ApplicationLayer + ?Sized>(&self, app: &Application) -> Duration {
|
||||
|
@ -463,8 +456,7 @@ impl Node {
|
|||
|
||||
for (_, rs) in roots.sets.iter() {
|
||||
for m in rs.members.iter() {
|
||||
if m.endpoints.is_some() && !address_collisions.contains(&m.identity.address) && !m.identity.eq(&self.identity)
|
||||
{
|
||||
if m.endpoints.is_some() && !address_collisions.contains(&m.identity.address) && !m.identity.eq(&self.identity) {
|
||||
debug_event!(
|
||||
app,
|
||||
"[vl1] examining root {} with {} endpoints",
|
||||
|
@ -551,11 +543,7 @@ impl Node {
|
|||
);
|
||||
*best_root = best.clone();
|
||||
} else {
|
||||
debug_event!(
|
||||
app,
|
||||
"[vl1] selected new best root: {} (was empty)",
|
||||
best.identity.address.to_string()
|
||||
);
|
||||
debug_event!(app, "[vl1] selected new best root: {} (was empty)", best.identity.address.to_string());
|
||||
let _ = best_root.insert(best.clone());
|
||||
}
|
||||
}
|
||||
|
@ -648,11 +636,7 @@ impl Node {
|
|||
|
||||
// Lock in write mode and remove dead paths, doing so piecemeal to again avoid blocking.
|
||||
for dp in dead_paths.iter() {
|
||||
self.paths
|
||||
.write()
|
||||
.unwrap()
|
||||
.get_mut::<PathMap<Application::LocalSocket>>()
|
||||
.remove(dp);
|
||||
self.paths.write().unwrap().get_mut::<PathMap<Application::LocalSocket>>().remove(dp);
|
||||
}
|
||||
|
||||
// Finally run keepalive sends as a batch.
|
||||
|
@ -704,15 +688,9 @@ impl Node {
|
|||
app,
|
||||
"[vl1] {} -> #{} {}->{} length {} (on socket {}@{})",
|
||||
source_endpoint.to_string(),
|
||||
packet
|
||||
.bytes_fixed_at::<8>(0)
|
||||
.map_or("????????????????".into(), |pid| hex::to_string(pid)),
|
||||
packet
|
||||
.bytes_fixed_at::<5>(13)
|
||||
.map_or("??????????".into(), |src| hex::to_string(src)),
|
||||
packet
|
||||
.bytes_fixed_at::<5>(8)
|
||||
.map_or("??????????".into(), |dest| hex::to_string(dest)),
|
||||
packet.bytes_fixed_at::<8>(0).map_or("????????????????".into(), |pid| hex::to_string(pid)),
|
||||
packet.bytes_fixed_at::<5>(13).map_or("??????????".into(), |src| hex::to_string(src)),
|
||||
packet.bytes_fixed_at::<5>(8).map_or("??????????".into(), |dest| hex::to_string(dest)),
|
||||
packet.len(),
|
||||
source_local_socket.to_string(),
|
||||
source_local_interface.to_string()
|
||||
|
@ -780,8 +758,7 @@ impl Node {
|
|||
for i in 1..assembled_packet.have {
|
||||
if let Some(f) = assembled_packet.frags[i as usize].as_ref() {
|
||||
if f.len() > v1::FRAGMENT_HEADER_SIZE {
|
||||
ok |=
|
||||
combined_packet.append_bytes(&f.as_bytes()[v1::FRAGMENT_HEADER_SIZE..]).is_ok();
|
||||
ok |= combined_packet.append_bytes(&f.as_bytes()[v1::FRAGMENT_HEADER_SIZE..]).is_ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -831,12 +808,7 @@ impl Node {
|
|||
#[cfg(debug_assertions)]
|
||||
{
|
||||
debug_packet_id = u64::from_be_bytes(packet_header.id);
|
||||
debug_event!(
|
||||
app,
|
||||
"[vl1] [v1] #{:0>16x} forwarding packet to {}",
|
||||
debug_packet_id,
|
||||
dest.to_string()
|
||||
);
|
||||
debug_event!(app, "[vl1] [v1] #{:0>16x} forwarding packet to {}", debug_packet_id, dest.to_string());
|
||||
}
|
||||
if packet_header.increment_hops() > v1::FORWARD_MAX_HOPS {
|
||||
#[cfg(debug_assertions)]
|
||||
|
@ -993,10 +965,7 @@ impl Node {
|
|||
time_ticks: i64,
|
||||
) -> Arc<Path> {
|
||||
let paths = self.paths.read().unwrap();
|
||||
if let Some(path) = paths
|
||||
.get::<PathMap<Application::LocalSocket>>()
|
||||
.get(&PathKey::Ref(ep, local_socket))
|
||||
{
|
||||
if let Some(path) = paths.get::<PathMap<Application::LocalSocket>>().get(&PathKey::Ref(ep, local_socket)) {
|
||||
path.clone()
|
||||
} else {
|
||||
drop(paths);
|
||||
|
|
|
@ -324,11 +324,7 @@ impl Peer {
|
|||
|
||||
let mut aes_gmac_siv = self.v1_proto_static_secret.aes_gmac_siv.get();
|
||||
aes_gmac_siv.encrypt_init(&self.v1_proto_next_message_id().to_be_bytes());
|
||||
aes_gmac_siv.encrypt_set_aad(&v1::get_packet_aad_bytes(
|
||||
self.identity.address,
|
||||
node.identity.address,
|
||||
flags_cipher_hops,
|
||||
));
|
||||
aes_gmac_siv.encrypt_set_aad(&v1::get_packet_aad_bytes(self.identity.address, node.identity.address, flags_cipher_hops));
|
||||
let payload = packet.as_bytes_starting_at_mut(v1::HEADER_SIZE).unwrap();
|
||||
aes_gmac_siv.encrypt_first_pass(payload);
|
||||
aes_gmac_siv.encrypt_first_pass_finish();
|
||||
|
@ -419,8 +415,7 @@ impl Peer {
|
|||
let message_id = self.v1_proto_next_message_id();
|
||||
|
||||
{
|
||||
let f: &mut (v1::PacketHeader, v1::message_component_structs::HelloFixedHeaderFields) =
|
||||
packet.append_struct_get_mut().unwrap();
|
||||
let f: &mut (v1::PacketHeader, v1::message_component_structs::HelloFixedHeaderFields) = packet.append_struct_get_mut().unwrap();
|
||||
f.0.id = message_id.to_ne_bytes();
|
||||
f.0.dest = self.identity.address.to_bytes();
|
||||
f.0.src = node.identity.address.to_bytes();
|
||||
|
@ -545,16 +540,9 @@ impl Peer {
|
|||
return match verb {
|
||||
message_type::VL1_NOP => PacketHandlerResult::Ok,
|
||||
message_type::VL1_HELLO => self.handle_incoming_hello(app, inner, node, time_ticks, message_id, source_path, &payload),
|
||||
message_type::VL1_ERROR => self.handle_incoming_error(
|
||||
app,
|
||||
inner,
|
||||
node,
|
||||
time_ticks,
|
||||
source_path,
|
||||
packet_header.hops(),
|
||||
message_id,
|
||||
&payload,
|
||||
),
|
||||
message_type::VL1_ERROR => {
|
||||
self.handle_incoming_error(app, inner, node, time_ticks, source_path, packet_header.hops(), message_id, &payload)
|
||||
}
|
||||
message_type::VL1_OK => self.handle_incoming_ok(
|
||||
app,
|
||||
inner,
|
||||
|
@ -567,13 +555,9 @@ impl Peer {
|
|||
&payload,
|
||||
),
|
||||
message_type::VL1_WHOIS => self.handle_incoming_whois(app, inner, node, time_ticks, message_id, &payload),
|
||||
message_type::VL1_RENDEZVOUS => {
|
||||
self.handle_incoming_rendezvous(app, node, time_ticks, message_id, source_path, &payload)
|
||||
}
|
||||
message_type::VL1_RENDEZVOUS => self.handle_incoming_rendezvous(app, node, time_ticks, message_id, source_path, &payload),
|
||||
message_type::VL1_ECHO => self.handle_incoming_echo(app, inner, node, time_ticks, message_id, &payload),
|
||||
message_type::VL1_PUSH_DIRECT_PATHS => {
|
||||
self.handle_incoming_push_direct_paths(app, node, time_ticks, source_path, &payload)
|
||||
}
|
||||
message_type::VL1_PUSH_DIRECT_PATHS => self.handle_incoming_push_direct_paths(app, node, time_ticks, source_path, &payload),
|
||||
message_type::VL1_USER_MESSAGE => self.handle_incoming_user_message(app, node, time_ticks, source_path, &payload),
|
||||
_ => inner.handle_packet(app, node, self, &source_path, packet_header.hops(), message_id, verb, &payload, 1),
|
||||
};
|
||||
|
@ -617,8 +601,7 @@ impl Peer {
|
|||
}
|
||||
|
||||
self.send(app, node, Some(source_path), time_ticks, |packet| -> Result<(), Infallible> {
|
||||
let f: &mut (OkHeader, v1::message_component_structs::OkHelloFixedHeaderFields) =
|
||||
packet.append_struct_get_mut().unwrap();
|
||||
let f: &mut (OkHeader, v1::message_component_structs::OkHelloFixedHeaderFields) = packet.append_struct_get_mut().unwrap();
|
||||
f.0.verb = message_type::VL1_OK;
|
||||
f.0.in_re_verb = message_type::VL1_HELLO;
|
||||
f.0.in_re_message_id = message_id.to_ne_bytes();
|
||||
|
@ -784,7 +767,7 @@ impl Peer {
|
|||
inner: &Inner,
|
||||
node: &Node,
|
||||
time_ticks: i64,
|
||||
message_id: MessageId,
|
||||
_message_id: MessageId,
|
||||
payload: &PacketBuffer,
|
||||
) -> PacketHandlerResult {
|
||||
if node.this_node_is_root() || inner.should_respond_to(&self.identity) {
|
||||
|
@ -813,12 +796,12 @@ impl Peer {
|
|||
|
||||
fn handle_incoming_rendezvous<Application: ApplicationLayer + ?Sized>(
|
||||
self: &Arc<Self>,
|
||||
app: &Application,
|
||||
_app: &Application,
|
||||
node: &Node,
|
||||
time_ticks: i64,
|
||||
message_id: MessageId,
|
||||
source_path: &Arc<Path>,
|
||||
payload: &PacketBuffer,
|
||||
_time_ticks: i64,
|
||||
_message_id: MessageId,
|
||||
_source_path: &Arc<Path>,
|
||||
_payload: &PacketBuffer,
|
||||
) -> PacketHandlerResult {
|
||||
if node.is_peer_root(self) {}
|
||||
return PacketHandlerResult::Ok;
|
||||
|
@ -853,22 +836,22 @@ impl Peer {
|
|||
|
||||
fn handle_incoming_push_direct_paths<Application: ApplicationLayer + ?Sized>(
|
||||
self: &Arc<Self>,
|
||||
app: &Application,
|
||||
node: &Node,
|
||||
time_ticks: i64,
|
||||
source_path: &Arc<Path>,
|
||||
payload: &PacketBuffer,
|
||||
_app: &Application,
|
||||
_node: &Node,
|
||||
_time_ticks: i64,
|
||||
_source_path: &Arc<Path>,
|
||||
_payload: &PacketBuffer,
|
||||
) -> PacketHandlerResult {
|
||||
PacketHandlerResult::Ok
|
||||
}
|
||||
|
||||
fn handle_incoming_user_message<Application: ApplicationLayer + ?Sized>(
|
||||
self: &Arc<Self>,
|
||||
app: &Application,
|
||||
node: &Node,
|
||||
time_ticks: i64,
|
||||
source_path: &Arc<Path>,
|
||||
payload: &PacketBuffer,
|
||||
_app: &Application,
|
||||
_node: &Node,
|
||||
_time_ticks: i64,
|
||||
_source_path: &Arc<Path>,
|
||||
_payload: &PacketBuffer,
|
||||
) -> PacketHandlerResult {
|
||||
PacketHandlerResult::Ok
|
||||
}
|
||||
|
|
|
@ -116,10 +116,7 @@ impl NetworkConfig {
|
|||
|
||||
d.set_u64(proto_v1_field_name::network_config::VERSION, 6);
|
||||
|
||||
d.set_str(
|
||||
proto_v1_field_name::network_config::NETWORK_ID,
|
||||
self.network_id.to_string().as_str(),
|
||||
);
|
||||
d.set_str(proto_v1_field_name::network_config::NETWORK_ID, self.network_id.to_string().as_str());
|
||||
if !self.name.is_empty() {
|
||||
d.set_str(proto_v1_field_name::network_config::NAME, self.name.as_str());
|
||||
}
|
||||
|
@ -253,8 +250,7 @@ impl NetworkConfig {
|
|||
|
||||
let mut nc = Self::new(nwid, issued_to_address);
|
||||
|
||||
d.get_str(proto_v1_field_name::network_config::NAME)
|
||||
.map(|x| nc.name = x.to_string());
|
||||
d.get_str(proto_v1_field_name::network_config::NAME).map(|x| nc.name = x.to_string());
|
||||
nc.private = d.get_str(proto_v1_field_name::network_config::TYPE).map_or(true, |x| x == "1");
|
||||
nc.timestamp = d
|
||||
.get_i64(proto_v1_field_name::network_config::TIMESTAMP)
|
||||
|
@ -358,16 +354,10 @@ impl NetworkConfig {
|
|||
authentication_expiry_time: d
|
||||
.get_i64(proto_v1_field_name::network_config::SSO_AUTHENTICATION_EXPIRY_TIME)
|
||||
.unwrap_or(0),
|
||||
issuer_url: d
|
||||
.get_str(proto_v1_field_name::network_config::SSO_ISSUER_URL)
|
||||
.unwrap_or("")
|
||||
.to_string(),
|
||||
issuer_url: d.get_str(proto_v1_field_name::network_config::SSO_ISSUER_URL).unwrap_or("").to_string(),
|
||||
nonce: d.get_str(proto_v1_field_name::network_config::SSO_NONCE).unwrap_or("").to_string(),
|
||||
state: d.get_str(proto_v1_field_name::network_config::SSO_STATE).unwrap_or("").to_string(),
|
||||
client_id: d
|
||||
.get_str(proto_v1_field_name::network_config::SSO_CLIENT_ID)
|
||||
.unwrap_or("")
|
||||
.to_string(),
|
||||
client_id: d.get_str(proto_v1_field_name::network_config::SSO_CLIENT_ID).unwrap_or("").to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -464,10 +454,7 @@ pub struct IpRoute {
|
|||
impl Marshalable for IpRoute {
|
||||
const MAX_MARSHAL_SIZE: usize = (InetAddress::MAX_MARSHAL_SIZE * 2) + 2 + 2;
|
||||
|
||||
fn marshal<const BL: usize>(
|
||||
&self,
|
||||
buf: &mut zerotier_utils::buffer::Buffer<BL>,
|
||||
) -> Result<(), zerotier_utils::marshalable::UnmarshalError> {
|
||||
fn marshal<const BL: usize>(&self, buf: &mut zerotier_utils::buffer::Buffer<BL>) -> Result<(), zerotier_utils::marshalable::UnmarshalError> {
|
||||
self.target.marshal(buf)?;
|
||||
if let Some(via) = self.via.as_ref() {
|
||||
via.marshal(buf)?;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#unstable_features = true
|
||||
max_width = 140
|
||||
max_width = 150
|
||||
#use_small_heuristics = "Max"
|
||||
edition = "2021"
|
||||
#empty_item_single_line = true
|
||||
|
|
|
@ -171,10 +171,7 @@ fn main() {
|
|||
if suggested.is_empty() {
|
||||
eprintln!("Unrecognized option '{}'. Use 'help' for help.", invalid);
|
||||
} else {
|
||||
eprintln!(
|
||||
"Unrecognized option '{}', did you mean {}? Use 'help' for help.",
|
||||
invalid, suggested
|
||||
);
|
||||
eprintln!("Unrecognized option '{}', did you mean {}? Use 'help' for help.", invalid, suggested);
|
||||
}
|
||||
}
|
||||
std::process::exit(exitcode::ERR_USAGE);
|
||||
|
@ -184,9 +181,7 @@ fn main() {
|
|||
|
||||
let flags = Flags {
|
||||
json_output: global_args.is_present("json"),
|
||||
base_path: global_args
|
||||
.value_of("path")
|
||||
.map_or_else(platform_default_home_path, |p| p.to_string()),
|
||||
base_path: global_args.value_of("path").map_or_else(platform_default_home_path, |p| p.to_string()),
|
||||
auth_token_path_override: global_args.value_of("token_path").map(|p| p.to_string()),
|
||||
auth_token_override: global_args.value_of("token").map(|t| t.to_string()),
|
||||
};
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md.
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* (c) ZeroTier, Inc.
|
||||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::io::Write;
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md.
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* (c) ZeroTier, Inc.
|
||||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md.
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* (c) ZeroTier, Inc.
|
||||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
use std::error::Error;
|
||||
use std::fmt::{Debug, Display};
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
/// Defer execution of a closure until dropped.
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* (c) ZeroTier, Inc.
|
||||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
struct Defer<F: FnOnce()>(Option<F>);
|
||||
|
||||
impl<F: FnOnce()> Drop for Defer<F> {
|
||||
|
@ -8,6 +15,9 @@ impl<F: FnOnce()> Drop for Defer<F> {
|
|||
}
|
||||
|
||||
/// Defer execution of a closure until the return value is dropped.
|
||||
///
|
||||
/// This mimics the defer statement in Go, allowing you to always do some cleanup at
|
||||
/// the end of a function no matter where it exits.
|
||||
pub fn defer<F: FnOnce()>(f: F) -> impl Drop {
|
||||
Defer(Some(f))
|
||||
}
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md.
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* (c) ZeroTier, Inc.
|
||||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::io::Write;
|
||||
|
@ -87,13 +93,11 @@ impl Dictionary {
|
|||
}
|
||||
|
||||
pub fn get_u64(&self, k: &str) -> Option<u64> {
|
||||
self.get_str(k)
|
||||
.map_or(None, |s| u64::from_str_radix(s, 16).map_or(None, |i| Some(i)))
|
||||
self.get_str(k).map_or(None, |s| u64::from_str_radix(s, 16).map_or(None, |i| Some(i)))
|
||||
}
|
||||
|
||||
pub fn get_i64(&self, k: &str) -> Option<i64> {
|
||||
self.get_str(k)
|
||||
.map_or(None, |s| i64::from_str_radix(s, 16).map_or(None, |i| Some(i)))
|
||||
self.get_str(k).map_or(None, |s| i64::from_str_radix(s, 16).map_or(None, |i| Some(i)))
|
||||
}
|
||||
|
||||
pub fn get_bool(&self, k: &str) -> Option<bool> {
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md.
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* (c) ZeroTier, Inc.
|
||||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
use std::error::Error;
|
||||
use std::fmt::{Debug, Display};
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md.
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* (c) ZeroTier, Inc.
|
||||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
// These were taken from BSD sysexits.h to provide some standard for process exit codes.
|
||||
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md.
|
||||
|
||||
//use std::sync::atomic::{AtomicI64, Ordering};
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* (c) ZeroTier, Inc.
|
||||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
/// Boolean rate limiter with normal (non-atomic) semantics.
|
||||
#[repr(transparent)]
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md.
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* (c) ZeroTier, Inc.
|
||||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
use std::mem::{size_of, MaybeUninit};
|
||||
use std::ptr::copy_nonoverlapping;
|
||||
|
@ -32,7 +38,7 @@ impl<T, const C: usize> GatherArray<T, C> {
|
|||
|
||||
/// Add an item to the array if we don't have this index anymore, returning complete array if all parts are here.
|
||||
#[inline(always)]
|
||||
pub fn add(&mut self, index: u8, value: T) -> Option<ArrayVec<T, C>> {
|
||||
pub fn add_return_when_satisfied(&mut self, index: u8, value: T) -> Option<ArrayVec<T, C>> {
|
||||
if index < self.goal {
|
||||
let mut have = self.have_bits;
|
||||
let got = 1u64.wrapping_shl(index as u32);
|
||||
|
@ -85,9 +91,9 @@ mod tests {
|
|||
for goal in 2u8..64u8 {
|
||||
let mut m = GatherArray::<u8, 64>::new(goal);
|
||||
for x in 0..(goal - 1) {
|
||||
assert!(m.add(x, x).is_none());
|
||||
assert!(m.add_return_when_satisfied(x, x).is_none());
|
||||
}
|
||||
let r = m.add(goal - 1, goal - 1).unwrap();
|
||||
let r = m.add_return_when_satisfied(goal - 1, goal - 1).unwrap();
|
||||
for x in 0..goal {
|
||||
assert_eq!(r.as_ref()[x as usize], x);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md.
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* (c) ZeroTier, Inc.
|
||||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
pub const HEX_CHARS: [u8; 16] = [
|
||||
b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'a', b'b', b'c', b'd', b'e', b'f',
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md.
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* (c) ZeroTier, Inc.
|
||||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* (c) ZeroTier, Inc.
|
||||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use serde_json::ser::Formatter;
|
||||
|
@ -36,11 +44,7 @@ pub fn json_patch(target: &mut serde_json::value::Value, source: &serde_json::va
|
|||
///
|
||||
/// If there are no changes, None is returned. The depth limit is passed through to json_patch and
|
||||
/// should be set to a sanity check value to prevent overflows.
|
||||
pub fn json_patch_object<O: Serialize + DeserializeOwned + Eq>(
|
||||
obj: O,
|
||||
patch: &str,
|
||||
depth_limit: usize,
|
||||
) -> Result<Option<O>, serde_json::Error> {
|
||||
pub fn json_patch_object<O: Serialize + DeserializeOwned + Eq>(obj: O, patch: &str, depth_limit: usize) -> Result<Option<O>, serde_json::Error> {
|
||||
serde_json::from_str::<serde_json::value::Value>(patch).map_or_else(
|
||||
|e| Err(e),
|
||||
|patch| {
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md.
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* (c) ZeroTier, Inc.
|
||||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
pub mod arrayvec;
|
||||
pub mod blob;
|
||||
|
@ -49,10 +55,7 @@ pub fn base64_decode_url_nopad(b64: &str) -> Option<Vec<u8>> {
|
|||
/// Get milliseconds since unix epoch.
|
||||
#[inline]
|
||||
pub fn ms_since_epoch() -> i64 {
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis() as i64
|
||||
std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis() as i64
|
||||
}
|
||||
|
||||
/// Get milliseconds since an arbitrary time in the past, guaranteed to monotonically increase.
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md.
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* (c) ZeroTier, Inc.
|
||||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
use std::error::Error;
|
||||
use std::fmt::{Debug, Display};
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md.
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* (c) ZeroTier, Inc.
|
||||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use std::mem::{needs_drop, size_of, MaybeUninit};
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md.
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* (c) ZeroTier, Inc.
|
||||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::ptr::NonNull;
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* (c) ZeroTier, Inc.
|
||||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md.
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* (c) ZeroTier, Inc.
|
||||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md.
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* (c) ZeroTier, Inc.
|
||||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::mem::MaybeUninit;
|
||||
|
@ -102,8 +108,8 @@ struct Entry<K: Eq + PartialEq + Hash + Clone, V> {
|
|||
/// buckets in the hash table. The maximum for both these parameters is 65535. This could be
|
||||
/// increased by making the index variables larger (e.g. u32 instead of u16).
|
||||
pub struct RingBufferMap<K: Eq + PartialEq + Hash + Clone, V, const C: usize, const B: usize> {
|
||||
salt: u32,
|
||||
entries: [Entry<K, V>; C],
|
||||
salt: u32,
|
||||
buckets: [u16; B],
|
||||
entry_ptr: u16,
|
||||
}
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* (c) ZeroTier, Inc.
|
||||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||
|
||||
/// Variant version of lock for RwLock with automatic conversion to a write lock as needed.
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md.
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* (c) ZeroTier, Inc.
|
||||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
use std::any::TypeId;
|
||||
use std::mem::{forget, size_of, MaybeUninit};
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md.
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* (c) ZeroTier, Inc.
|
||||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
use std::io::{Read, Write};
|
||||
|
||||
|
|
|
@ -2,15 +2,15 @@
|
|||
|
||||
/// A list of unassigned or obsolete ports under 1024 that could possibly be squatted.
|
||||
pub const UNASSIGNED_PRIVILEGED_PORTS: [u16; 299] = [
|
||||
4, 6, 8, 10, 12, 14, 15, 16, 26, 28, 30, 32, 34, 36, 40, 60, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 285, 288, 289, 290,
|
||||
291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332,
|
||||
334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 703, 708, 713, 714, 715, 716, 717, 718, 719, 720, 721, 722, 723, 724, 725, 726, 727,
|
||||
728, 732, 733, 734, 735, 736, 737, 738, 739, 740, 743, 745, 746, 755, 756, 766, 768, 778, 779, 781, 782, 783, 784, 785, 786, 787, 788,
|
||||
789, 790, 791, 792, 793, 794, 795, 796, 797, 798, 799, 802, 803, 804, 805, 806, 807, 808, 809, 811, 812, 813, 814, 815, 816, 817, 818,
|
||||
819, 820, 821, 822, 823, 824, 825, 826, 827, 834, 835, 836, 837, 838, 839, 840, 841, 842, 843, 844, 845, 846, 849, 850, 851, 852, 853,
|
||||
854, 855, 856, 857, 858, 859, 862, 863, 864, 865, 866, 867, 868, 869, 870, 871, 872, 874, 875, 876, 877, 878, 879, 880, 881, 882, 883,
|
||||
884, 885, 889, 890, 891, 892, 893, 894, 895, 896, 897, 898, 899, 904, 905, 906, 907, 908, 909, 910, 911, 914, 915, 916, 917, 918, 919,
|
||||
920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946,
|
||||
947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973,
|
||||
974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1023,
|
||||
4, 6, 8, 10, 12, 14, 15, 16, 26, 28, 30, 32, 34, 36, 40, 60, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 285, 288, 289, 290, 291, 292,
|
||||
293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 334, 335, 336, 337,
|
||||
338, 339, 340, 341, 342, 343, 703, 708, 713, 714, 715, 716, 717, 718, 719, 720, 721, 722, 723, 724, 725, 726, 727, 728, 732, 733, 734, 735, 736,
|
||||
737, 738, 739, 740, 743, 745, 746, 755, 756, 766, 768, 778, 779, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796,
|
||||
797, 798, 799, 802, 803, 804, 805, 806, 807, 808, 809, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 834,
|
||||
835, 836, 837, 838, 839, 840, 841, 842, 843, 844, 845, 846, 849, 850, 851, 852, 853, 854, 855, 856, 857, 858, 859, 862, 863, 864, 865, 866, 867,
|
||||
868, 869, 870, 871, 872, 874, 875, 876, 877, 878, 879, 880, 881, 882, 883, 884, 885, 889, 890, 891, 892, 893, 894, 895, 896, 897, 898, 899, 904,
|
||||
905, 906, 907, 908, 909, 910, 911, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, 931, 932, 933, 934, 935,
|
||||
936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964,
|
||||
965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 1001, 1002, 1003, 1004,
|
||||
1005, 1006, 1007, 1008, 1009, 1023,
|
||||
];
|
||||
|
|
|
@ -31,11 +31,7 @@ fn socket_read_concurrency() -> usize {
|
|||
unsafe {
|
||||
let mut t = THREADS_PER_SOCKET;
|
||||
if t == 0 {
|
||||
t = std::thread::available_parallelism()
|
||||
.unwrap()
|
||||
.get()
|
||||
.max(1)
|
||||
.min(MAX_PER_SOCKET_CONCURRENCY);
|
||||
t = std::thread::available_parallelism().unwrap().get().max(1).min(MAX_PER_SOCKET_CONCURRENCY);
|
||||
THREADS_PER_SOCKET = t;
|
||||
}
|
||||
t
|
||||
|
@ -43,13 +39,7 @@ fn socket_read_concurrency() -> usize {
|
|||
}
|
||||
|
||||
pub trait UdpPacketHandler: Send + Sync {
|
||||
fn incoming_udp_packet(
|
||||
self: &Arc<Self>,
|
||||
time_ticks: i64,
|
||||
socket: &Arc<BoundUdpSocket>,
|
||||
source_address: &InetAddress,
|
||||
packet: PooledPacketBuffer,
|
||||
);
|
||||
fn incoming_udp_packet(self: &Arc<Self>, time_ticks: i64, socket: &Arc<BoundUdpSocket>, source_address: &InetAddress, packet: PooledPacketBuffer);
|
||||
}
|
||||
|
||||
/// A local port to which one or more UDP sockets is bound.
|
||||
|
|
|
@ -113,8 +113,7 @@ impl<Inner: InnerProtocolLayer + ?Sized + 'static> VL1Service<Inner> {
|
|||
if !state.settings.fixed_ports.contains(p) {
|
||||
let (total_smart_ptr_handles, most_recent_receive) = s.read().unwrap().liveness();
|
||||
if total_smart_ptr_handles < most_stale_binding_liveness.0
|
||||
|| (total_smart_ptr_handles == most_stale_binding_liveness.0
|
||||
&& most_recent_receive <= most_stale_binding_liveness.1)
|
||||
|| (total_smart_ptr_handles == most_stale_binding_liveness.0 && most_recent_receive <= most_stale_binding_liveness.1)
|
||||
{
|
||||
most_stale_binding_liveness.0 = total_smart_ptr_handles;
|
||||
most_stale_binding_liveness.1 = most_recent_receive;
|
||||
|
|
|
@ -27,8 +27,7 @@ pub struct VL1Settings {
|
|||
|
||||
impl VL1Settings {
|
||||
#[cfg(target_os = "macos")]
|
||||
pub const DEFAULT_PREFIX_BLACKLIST: [&'static str; 11] =
|
||||
["lo", "utun", "gif", "stf", "iptap", "pktap", "feth", "zt", "llw", "anpi", "bridge"];
|
||||
pub const DEFAULT_PREFIX_BLACKLIST: [&'static str; 11] = ["lo", "utun", "gif", "stf", "iptap", "pktap", "feth", "zt", "llw", "anpi", "bridge"];
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub const DEFAULT_PREFIX_BLACKLIST: [&'static str; 5] = ["lo", "tun", "tap", "ipsec", "zt"];
|
||||
|
|
|
@ -5,7 +5,23 @@ license = "MPL-2.0"
|
|||
name = "zssp"
|
||||
version = "0.1.0"
|
||||
|
||||
#[profile.release]
|
||||
#opt-level = 3
|
||||
#lto = true
|
||||
#codegen-units = 1
|
||||
#panic = 'abort'
|
||||
|
||||
[lib]
|
||||
name = "zssp"
|
||||
path = "src/lib.rs"
|
||||
doc = true
|
||||
|
||||
[[bin]]
|
||||
name = "zssp_test"
|
||||
path = "src/main.rs"
|
||||
doc = false
|
||||
|
||||
[dependencies]
|
||||
zerotier-utils = { path = "../utils" }
|
||||
zerotier-crypto = { path = "../crypto" }
|
||||
pqc_kyber = { git = "https://github.com/Argyle-Software/kyber", rev = "8c7927e00f4e3508769bf69afd55b2be1c22884d", features = ["kyber1024", "std"], default-features = false }
|
||||
pqc_kyber = { version = "0.4.0", default-features = false, features = ["kyber1024", "std"] }
|
||||
|
|
|
@ -1,8 +1,2 @@
|
|||
ZeroTier Secure Socket Protocol
|
||||
======
|
||||
|
||||
**NOTE: this protocol and code have not yet been formally audited and should not be used in anything production.**
|
||||
|
||||
ZSSP (ZeroTier Secure Socket Protocol) is an implementation of the Noise_IK pattern using FIPS/NIST compliant primitives. After Noise_IK negotiation is complete ZSSP also adds key ratcheting and optional (enabled by default) support for quantum data forward secrecy with Kyber1024.
|
||||
|
||||
It's general purpose and could be used with any system but contains a few specific design choices to make it optimal for ZeroTier and easy to distinguish from legacy ZeroTier V1 traffic for backward compatibility.
|
||||
|
|
|
@ -1,37 +1,77 @@
|
|||
use std::ops::Deref;
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* (c) ZeroTier, Inc.
|
||||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
use zerotier_crypto::{
|
||||
p384::{P384KeyPair, P384PublicKey},
|
||||
secret::Secret,
|
||||
};
|
||||
use std::hash::Hash;
|
||||
|
||||
use crate::{
|
||||
sessionid::SessionId,
|
||||
zssp::{ReceiveContext, Session},
|
||||
};
|
||||
use zerotier_crypto::p384::P384KeyPair;
|
||||
|
||||
/// Trait to implement to integrate the session into an application.
|
||||
///
|
||||
/// Templating the session on this trait lets the code here be almost entirely transport, OS,
|
||||
/// and use case independent.
|
||||
///
|
||||
/// The constants exposed in this trait can be redefined from their defaults to change rekey
|
||||
/// and negotiation timeout behavior. This is discouraged except for testing purposes when low
|
||||
/// key lifetime values may be desirable to test rekeying. Also note that each side takes turns
|
||||
/// initiating rekey, so if both sides don't have the same values you'll get asymmetric timing
|
||||
/// behavior. This will still work as long as the key usage counter doesn't exceed the
|
||||
/// EXPIRE_AFTER_USES limit.
|
||||
pub trait ApplicationLayer: Sized {
|
||||
/// Arbitrary opaque object associated with a session, such as a connection state object.
|
||||
/// Rekey after this many key uses.
|
||||
///
|
||||
/// The default is 1/4 the recommended NIST limit for AES-GCM. Unless you are transferring
|
||||
/// a massive amount of data REKEY_AFTER_TIME_MS is probably going to kick in first.
|
||||
const REKEY_AFTER_USES: u64 = 536870912;
|
||||
|
||||
/// Hard expiration after this many uses.
|
||||
///
|
||||
/// Attempting to encrypt more than this many messages with a key will cause a hard error
|
||||
/// and the internal erasure of ephemeral key material. You'll only ever hit this if something
|
||||
/// goes wrong and rekeying fails.
|
||||
const EXPIRE_AFTER_USES: u64 = 2147483647;
|
||||
|
||||
/// Start attempting to rekey after a key has been in use for this many milliseconds.
|
||||
///
|
||||
/// Default is two hours.
|
||||
const REKEY_AFTER_TIME_MS: i64 = 1000 * 60 * 60 * 2;
|
||||
|
||||
/// Maximum random jitter to add to rekey-after time.
|
||||
///
|
||||
/// Default is ten minutes.
|
||||
const REKEY_AFTER_TIME_MS_MAX_JITTER: u32 = 1000 * 60 * 10;
|
||||
|
||||
/// Timeout for incoming Noise_XK session negotiation in milliseconds.
|
||||
///
|
||||
/// Default is two seconds, which should be enough for even extremely slow links or links
|
||||
/// over very long distances.
|
||||
const INCOMING_SESSION_NEGOTIATION_TIMEOUT_MS: i64 = 2000;
|
||||
|
||||
/// Retry interval for outgoing connection initiation or rekey attempts.
|
||||
///
|
||||
/// Retry attepmpts will be no more often than this, but the delay may end up being slightly more
|
||||
/// in some cases depending on where in the cycle the initial attempt falls.
|
||||
const RETRY_INTERVAL: i64 = 500;
|
||||
|
||||
/// Type for arbitrary opaque object for use by the application that is attached to each session.
|
||||
type Data;
|
||||
|
||||
/// Arbitrary object that dereferences to the session, such as Arc<Session<Self>>.
|
||||
type SessionRef<'a>: Deref<Target = Session<Self>>;
|
||||
|
||||
/// A buffer containing data read from the network that can be cached.
|
||||
/// Data type for incoming packet buffers.
|
||||
///
|
||||
/// This can be e.g. a pooled buffer that automatically returns itself to the pool when dropped.
|
||||
/// It can also just be a Vec<u8> or Box<[u8]> or something like that.
|
||||
/// This can be something like Vec<u8> or Box<[u8]> or it can be something like a pooled reusable
|
||||
/// buffer that automatically returns to its pool when ZSSP is done with it. ZSSP may hold these
|
||||
/// for a short period of time when assembling fragmented packets on the receive path.
|
||||
type IncomingPacketBuffer: AsRef<[u8]> + AsMut<[u8]>;
|
||||
|
||||
/// Remote physical address on whatever transport this session is using.
|
||||
type RemoteAddress;
|
||||
|
||||
/// Rate limit for attempts to rekey existing sessions in milliseconds (default: 2000).
|
||||
const REKEY_RATE_LIMIT_MS: i64 = 2000;
|
||||
/// Opaque type for whatever constitutes a physical path to the application.
|
||||
///
|
||||
/// A physical path could be an IP address or IP plus device in the case of UDP, a socket in the
|
||||
/// case of TCP, etc.
|
||||
type PhysicalPath: PartialEq + Eq + Hash + Clone;
|
||||
|
||||
/// Get a reference to this host's static public key blob.
|
||||
///
|
||||
|
@ -39,39 +79,8 @@ pub trait ApplicationLayer: Sized {
|
|||
/// is a byte serialized identity. It could just be a naked NIST P-384 key if that's all you need.
|
||||
fn get_local_s_public_blob(&self) -> &[u8];
|
||||
|
||||
/// Get SHA384(this host's static public key blob).
|
||||
///
|
||||
/// This allows us to avoid computing SHA384(public key blob) over and over again.
|
||||
fn get_local_s_public_blob_hash(&self) -> &[u8; 48];
|
||||
|
||||
/// Get a reference to this hosts' static public key's NIST P-384 secret key pair.
|
||||
/// Get a reference to this host's static public key's NIST P-384 secret key pair.
|
||||
///
|
||||
/// This must return the NIST P-384 public key that is contained within the static public key blob.
|
||||
fn get_local_s_keypair(&self) -> &P384KeyPair;
|
||||
|
||||
/// Extract the NIST P-384 ECC public key component from a static public key blob or return None on failure.
|
||||
///
|
||||
/// This is called to parse the static public key blob from the other end and extract its NIST P-384 public
|
||||
/// key. SECURITY NOTE: the information supplied here is from the wire so care must be taken to parse it
|
||||
/// safely and fail on any error or corruption.
|
||||
fn extract_s_public_from_raw(static_public: &[u8]) -> Option<P384PublicKey>;
|
||||
|
||||
/// Look up a local session by local session ID or return None if not found.
|
||||
fn lookup_session<'a>(&self, local_session_id: SessionId) -> Option<Self::SessionRef<'a>>;
|
||||
|
||||
/// Rate limit and check an attempted new session (called before accept_new_session).
|
||||
fn check_new_session(&self, rc: &ReceiveContext<Self>, remote_address: &Self::RemoteAddress) -> bool;
|
||||
|
||||
/// Check whether a new session should be accepted.
|
||||
///
|
||||
/// On success a tuple of local session ID, static secret, and associated object is returned. The
|
||||
/// static secret is whatever results from agreement between the local and remote static public
|
||||
/// keys.
|
||||
fn accept_new_session(
|
||||
&self,
|
||||
receive_context: &ReceiveContext<Self>,
|
||||
remote_address: &Self::RemoteAddress,
|
||||
remote_static_public: &[u8],
|
||||
remote_metadata: &[u8],
|
||||
) -> Option<(SessionId, Secret<64>, Self::Data)>;
|
||||
}
|
||||
|
|
|
@ -1,113 +0,0 @@
|
|||
/// Minimum size of a valid physical ZSSP packet or packet fragment.
|
||||
pub const MIN_PACKET_SIZE: usize = HEADER_SIZE + AES_GCM_TAG_SIZE;
|
||||
|
||||
/// Minimum physical MTU for ZSSP to function.
|
||||
pub const MIN_TRANSPORT_MTU: usize = 64;
|
||||
|
||||
/// Minimum recommended interval between calls to service() on each session, in milliseconds.
|
||||
pub const SERVICE_INTERVAL: u64 = 10000;
|
||||
|
||||
/// Setting this to true enables kyber1024 post-quantum forward secrecy.
|
||||
///
|
||||
/// Kyber1024 is used for data forward secrecy but not authentication. Authentication would
|
||||
/// require Kyber1024 in identities, which would make them huge, and isn't needed for our
|
||||
/// threat model which is data warehousing today to decrypt tomorrow. Breaking authentication
|
||||
/// is only relevant today, not in some mid to far future where a QC that can break 384-bit ECC
|
||||
/// exists.
|
||||
///
|
||||
/// This is normally enabled but could be disabled at build time for e.g. very small devices.
|
||||
/// It might not even be necessary there to disable it since it's not that big and is usually
|
||||
/// faster than NIST P-384 ECDH.
|
||||
pub(crate) const JEDI: bool = true;
|
||||
|
||||
/// Maximum number of fragments for data packets.
|
||||
pub(crate) const MAX_FRAGMENTS: usize = 48; // hard protocol max: 63
|
||||
|
||||
/// Maximum number of fragments for key exchange packets (can be smaller to save memory, only a few needed)
|
||||
pub(crate) const KEY_EXCHANGE_MAX_FRAGMENTS: usize = 2; // enough room for p384 + ZT identity + kyber1024 + tag/hmac/etc.
|
||||
|
||||
/// Start attempting to rekey after a key has been used to send packets this many times.
|
||||
/// This is 1/4 the recommended NIST limit for AES-GCM key lifetimes under most conditions.
|
||||
pub(crate) const REKEY_AFTER_USES: u64 = 536870912;
|
||||
|
||||
/// Hard expiration after this many uses.
|
||||
///
|
||||
/// Use of the key beyond this point is prohibited. If we reach this number of key uses
|
||||
/// the key will be destroyed in memory and the session will cease to function. A hard
|
||||
/// error is also generated.
|
||||
pub(crate) const EXPIRE_AFTER_USES: u64 = REKEY_AFTER_USES * 2;
|
||||
|
||||
/// Start attempting to rekey after a key has been in use for this many milliseconds.
|
||||
pub(crate) const REKEY_AFTER_TIME_MS: i64 = 1000 * 60 * 60; // 1 hour
|
||||
|
||||
/// Maximum random jitter to add to rekey-after time.
|
||||
pub(crate) const REKEY_AFTER_TIME_MS_MAX_JITTER: u32 = 1000 * 60 * 10; // 10 minutes
|
||||
|
||||
/// Version 0: AES-256-GCM + NIST P-384 + optional Kyber1024 PQ forward secrecy
|
||||
pub(crate) const SESSION_PROTOCOL_VERSION: u8 = 0x00;
|
||||
|
||||
/// Secondary key type: none, use only P-384 for forward secrecy.
|
||||
pub(crate) const HYBRID_KEY_TYPE_NONE: u8 = 0;
|
||||
|
||||
/// Secondary key type: Kyber1024, PQ forward secrecy enabled.
|
||||
pub(crate) const HYBRID_KEY_TYPE_KYBER1024: u8 = 1;
|
||||
|
||||
/// Size of packet header
|
||||
pub(crate) const HEADER_SIZE: usize = 16;
|
||||
|
||||
/// Start of single block AES encryption of a portion of the header (and some data).
|
||||
pub(crate) const HEADER_CHECK_ENCRYPT_START: usize = 6;
|
||||
|
||||
/// End of single block AES encryption of a portion of the header (and some data).
|
||||
pub(crate) const HEADER_CHECK_ENCRYPT_END: usize = 22;
|
||||
|
||||
/// Size of AES-GCM keys (256 bits)
|
||||
pub(crate) const AES_KEY_SIZE: usize = 32;
|
||||
|
||||
/// Size of AES-GCM MAC tags
|
||||
pub(crate) const AES_GCM_TAG_SIZE: usize = 16;
|
||||
|
||||
/// Size of HMAC-SHA384 MAC tags
|
||||
pub(crate) const HMAC_SIZE: usize = 48;
|
||||
|
||||
/// Size of a session ID, which behaves a bit like a TCP port number.
|
||||
///
|
||||
/// This is large since some ZeroTier nodes handle huge numbers of links, like roots and controllers.
|
||||
pub(crate) const SESSION_ID_SIZE: usize = 6;
|
||||
|
||||
/// Maximum difference between out-of-order incoming packet counters, and size of deduplication buffer.
|
||||
pub(crate) const COUNTER_WINDOW_MAX_OUT_OF_ORDER: usize = 16;
|
||||
|
||||
/// Maximum skip-ahead for counter.
|
||||
///
|
||||
/// This is huge (2^24) because its real purpose is to filter out bad packets where decryption of
|
||||
/// the counter yields an invalid value.
|
||||
pub(crate) const COUNTER_WINDOW_MAX_SKIP_AHEAD: u64 = 16777216;
|
||||
|
||||
// Packet types can range from 0 to 15 (4 bits) -- 0-3 are defined and 4-15 are reserved for future use
|
||||
pub(crate) const PACKET_TYPE_DATA: u8 = 0;
|
||||
pub(crate) const PACKET_TYPE_INITIAL_KEY_OFFER: u8 = 1; // "alice"
|
||||
pub(crate) const PACKET_TYPE_KEY_COUNTER_OFFER: u8 = 2; // "bob"
|
||||
|
||||
// Key usage labels for sub-key derivation using NIST-style KBKDF (basically just HMAC KDF).
|
||||
pub(crate) const KBKDF_KEY_USAGE_LABEL_HMAC: u8 = b'M'; // HMAC-SHA384 authentication for key exchanges
|
||||
pub(crate) const KBKDF_KEY_USAGE_LABEL_HEADER_CHECK: u8 = b'H'; // AES-based header check code generation
|
||||
pub(crate) const KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB: u8 = b'A'; // AES-GCM in A->B direction
|
||||
pub(crate) const KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE: u8 = b'B'; // AES-GCM in B->A direction
|
||||
pub(crate) const KBKDF_KEY_USAGE_LABEL_RATCHETING: u8 = b'R'; // Key input for next ephemeral ratcheting
|
||||
|
||||
// AES key size for header check code generation
|
||||
pub(crate) const HEADER_CHECK_AES_KEY_SIZE: usize = 16;
|
||||
|
||||
/// Aribitrary starting value for master key derivation.
|
||||
///
|
||||
/// It doesn't matter very much what this is but it's good for it to be unique. It should
|
||||
/// be changed if this code is changed in any cryptographically meaningful way like changing
|
||||
/// the primary algorithm from NIST P-384 or the transport cipher from AES-GCM.
|
||||
pub(crate) const INITIAL_KEY: [u8; 64] = [
|
||||
// macOS command line to generate:
|
||||
// echo -n 'ZSSP_Noise_IKpsk2_NISTP384_?KYBER1024_AESGCM_SHA512' | shasum -a 512 | cut -d ' ' -f 1 | xxd -r -p | xxd -i
|
||||
0x35, 0x6a, 0x75, 0xc0, 0xbf, 0xbe, 0xc3, 0x59, 0x70, 0x94, 0x50, 0x69, 0x4c, 0xa2, 0x08, 0x40, 0xc7, 0xdf, 0x67, 0xa8, 0x68, 0x52,
|
||||
0x6e, 0xd5, 0xdd, 0x77, 0xec, 0x59, 0x6f, 0x8e, 0xa1, 0x99, 0xb4, 0x32, 0x85, 0xaf, 0x7f, 0x0d, 0xa9, 0x6c, 0x01, 0xfb, 0x72, 0x46,
|
||||
0xc0, 0x09, 0x58, 0xb8, 0xe0, 0xa8, 0xcf, 0xb1, 0x58, 0x04, 0x6e, 0x32, 0xba, 0xa8, 0xb8, 0xf9, 0x0a, 0xa4, 0xbf, 0x36,
|
||||
];
|
|
@ -1,8 +1,15 @@
|
|||
use crate::sessionid::SessionId;
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* (c) ZeroTier, Inc.
|
||||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub enum Error {
|
||||
/// The packet was addressed to an unrecognized local session (should usually be ignored)
|
||||
UnknownLocalSessionId(SessionId),
|
||||
UnknownLocalSessionId,
|
||||
|
||||
/// Packet was not well formed
|
||||
InvalidPacket,
|
||||
|
@ -15,18 +22,12 @@ pub enum Error {
|
|||
/// There is a safe way to reply if absolutely necessary, by sending the reply back after a constant amount of time, but this is difficult to get correct.
|
||||
FailedAuthentication,
|
||||
|
||||
/// New session was rejected by the application layer.
|
||||
NewSessionRejected,
|
||||
|
||||
/// Rekeying failed and session secret has reached its hard usage count limit
|
||||
MaxKeyLifetimeExceeded,
|
||||
|
||||
/// Attempt to send using session without established key
|
||||
SessionNotEstablished,
|
||||
|
||||
/// Packet ignored by rate limiter.
|
||||
RateLimited,
|
||||
|
||||
/// The other peer specified an unrecognized protocol version
|
||||
UnknownProtocolVersion,
|
||||
|
||||
|
@ -36,6 +37,9 @@ pub enum Error {
|
|||
/// Data object is too large to send, even with fragmentation
|
||||
DataTooLarge,
|
||||
|
||||
/// Packet counter was outside window or packet arrived with session in an unexpected state.
|
||||
OutOfSequence,
|
||||
|
||||
/// An unexpected buffer overrun occured while attempting to encode or decode a packet.
|
||||
///
|
||||
/// This can only ever happen if exceptionally large key blobs or metadata are being used,
|
||||
|
@ -43,22 +47,29 @@ pub enum Error {
|
|||
UnexpectedBufferOverrun,
|
||||
}
|
||||
|
||||
// An I/O error in the parser means an invalid packet.
|
||||
impl From<std::io::Error> for Error {
|
||||
#[inline(always)]
|
||||
fn from(_: std::io::Error) -> Self {
|
||||
Self::InvalidPacket
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::UnknownLocalSessionId(id) => f.write_str(format!("UnknownLocalSessionId({})", id).as_str()),
|
||||
Self::InvalidPacket => f.write_str("InvalidPacket"),
|
||||
Self::InvalidParameter => f.write_str("InvalidParameter"),
|
||||
Self::FailedAuthentication => f.write_str("FailedAuthentication"),
|
||||
Self::NewSessionRejected => f.write_str("NewSessionRejected"),
|
||||
Self::MaxKeyLifetimeExceeded => f.write_str("MaxKeyLifetimeExceeded"),
|
||||
Self::SessionNotEstablished => f.write_str("SessionNotEstablished"),
|
||||
Self::RateLimited => f.write_str("RateLimited"),
|
||||
Self::UnknownProtocolVersion => f.write_str("UnknownProtocolVersion"),
|
||||
Self::DataBufferTooSmall => f.write_str("DataBufferTooSmall"),
|
||||
Self::DataTooLarge => f.write_str("DataTooLarge"),
|
||||
Self::UnexpectedBufferOverrun => f.write_str("UnexpectedBufferOverrun"),
|
||||
}
|
||||
f.write_str(match self {
|
||||
Self::UnknownLocalSessionId => "UnknownLocalSessionId",
|
||||
Self::InvalidPacket => "InvalidPacket",
|
||||
Self::InvalidParameter => "InvalidParameter",
|
||||
Self::FailedAuthentication => "FailedAuthentication",
|
||||
Self::MaxKeyLifetimeExceeded => "MaxKeyLifetimeExceeded",
|
||||
Self::SessionNotEstablished => "SessionNotEstablished",
|
||||
Self::UnknownProtocolVersion => "UnknownProtocolVersion",
|
||||
Self::DataBufferTooSmall => "DataBufferTooSmall",
|
||||
Self::DataTooLarge => "DataTooLarge",
|
||||
Self::OutOfSequence => "OutOfSequence",
|
||||
Self::UnexpectedBufferOverrun => "UnexpectedBufferOverrun",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
107
zssp/src/fragged.rs
Normal file
107
zssp/src/fragged.rs
Normal file
|
@ -0,0 +1,107 @@
|
|||
use std::mem::{needs_drop, size_of, zeroed, MaybeUninit};
|
||||
use std::ptr::slice_from_raw_parts;
|
||||
|
||||
/// Fast packet defragmenter
|
||||
pub struct Fragged<Fragment, const MAX_FRAGMENTS: usize> {
|
||||
have: u64,
|
||||
counter: u64,
|
||||
frags: [MaybeUninit<Fragment>; MAX_FRAGMENTS],
|
||||
}
|
||||
|
||||
pub struct Assembled<Fragment, const MAX_FRAGMENTS: usize>([MaybeUninit<Fragment>; MAX_FRAGMENTS], usize);
|
||||
|
||||
impl<Fragment, const MAX_FRAGMENTS: usize> AsRef<[Fragment]> for Assembled<Fragment, MAX_FRAGMENTS> {
|
||||
#[inline(always)]
|
||||
fn as_ref(&self) -> &[Fragment] {
|
||||
unsafe { &*slice_from_raw_parts(self.0.as_ptr().cast::<Fragment>(), self.1) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<Fragment, const MAX_FRAGMENTS: usize> Drop for Assembled<Fragment, MAX_FRAGMENTS> {
|
||||
#[inline(always)]
|
||||
fn drop(&mut self) {
|
||||
for i in 0..self.1 {
|
||||
unsafe {
|
||||
self.0.get_unchecked_mut(i).assume_init_drop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Fragment, const MAX_FRAGMENTS: usize> Fragged<Fragment, MAX_FRAGMENTS> {
|
||||
#[inline(always)]
|
||||
pub fn new() -> Self {
|
||||
debug_assert!(MAX_FRAGMENTS <= 64);
|
||||
debug_assert_eq!(size_of::<MaybeUninit<Fragment>>(), size_of::<Fragment>());
|
||||
debug_assert_eq!(
|
||||
size_of::<[MaybeUninit<Fragment>; MAX_FRAGMENTS]>(),
|
||||
size_of::<[Fragment; MAX_FRAGMENTS]>()
|
||||
);
|
||||
unsafe { zeroed() }
|
||||
}
|
||||
|
||||
/// Add a fragment and return an assembled packet container if all fragments have been received.
|
||||
///
|
||||
/// When a fully assembled packet is returned the internal state is reset and this object can
|
||||
/// be reused to assemble another packet.
|
||||
#[inline(always)]
|
||||
pub fn assemble(&mut self, counter: u64, fragment: Fragment, fragment_no: u8, fragment_count: u8) -> Option<Assembled<Fragment, MAX_FRAGMENTS>> {
|
||||
if fragment_no < fragment_count && (fragment_count as usize) <= MAX_FRAGMENTS {
|
||||
debug_assert!((fragment_count as usize) <= MAX_FRAGMENTS);
|
||||
debug_assert!((fragment_no as usize) < MAX_FRAGMENTS);
|
||||
|
||||
let mut have = self.have;
|
||||
if counter != self.counter {
|
||||
self.counter = counter;
|
||||
if needs_drop::<Fragment>() {
|
||||
let mut i = 0;
|
||||
while have != 0 {
|
||||
if (have & 1) != 0 {
|
||||
debug_assert!(i < MAX_FRAGMENTS);
|
||||
unsafe { self.frags.get_unchecked_mut(i).assume_init_drop() };
|
||||
}
|
||||
have = have.wrapping_shr(1);
|
||||
i += 1;
|
||||
}
|
||||
} else {
|
||||
have = 0;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
self.frags.get_unchecked_mut(fragment_no as usize).write(fragment);
|
||||
}
|
||||
|
||||
let want = 0xffffffffffffffffu64.wrapping_shr((64 - fragment_count) as u32);
|
||||
have |= 1u64.wrapping_shl(fragment_no as u32);
|
||||
if (have & want) == want {
|
||||
self.have = 0;
|
||||
// Setting 'have' to 0 resets the state of this object, and the fragments
|
||||
// are effectively moved into the Assembled<> container and returned. That
|
||||
// container will drop them when it is dropped.
|
||||
return Some(Assembled(unsafe { std::mem::transmute_copy(&self.frags) }, fragment_count as usize));
|
||||
} else {
|
||||
self.have = have;
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
impl<Fragment, const MAX_FRAGMENTS: usize> Drop for Fragged<Fragment, MAX_FRAGMENTS> {
|
||||
#[inline(always)]
|
||||
fn drop(&mut self) {
|
||||
if needs_drop::<Fragment>() {
|
||||
let mut have = self.have;
|
||||
let mut i = 0;
|
||||
while have != 0 {
|
||||
if (have & 1) != 0 {
|
||||
debug_assert!(i < MAX_FRAGMENTS);
|
||||
unsafe { self.frags.get_unchecked_mut(i).assume_init_drop() };
|
||||
}
|
||||
have = have.wrapping_shr(1);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +1,20 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* (c) ZeroTier, Inc.
|
||||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
mod applicationlayer;
|
||||
mod error;
|
||||
mod fragged;
|
||||
mod proto;
|
||||
mod sessionid;
|
||||
mod tests;
|
||||
mod zssp;
|
||||
|
||||
pub mod constants;
|
||||
|
||||
pub use crate::applicationlayer::ApplicationLayer;
|
||||
pub use crate::error::Error;
|
||||
pub use crate::proto::{MAX_INIT_PAYLOAD_SIZE, MIN_PACKET_SIZE, MIN_TRANSPORT_MTU};
|
||||
pub use crate::sessionid::SessionId;
|
||||
pub use crate::zssp::{ReceiveContext, ReceiveResult, Role, Session};
|
||||
pub use crate::zssp::{Context, ReceiveResult, Session};
|
||||
|
|
283
zssp/src/main.rs
Normal file
283
zssp/src/main.rs
Normal file
|
@ -0,0 +1,283 @@
|
|||
use std::iter::ExactSizeIterator;
|
||||
use std::str::FromStr;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use zerotier_crypto::p384::{P384KeyPair, P384PublicKey};
|
||||
use zerotier_crypto::random;
|
||||
use zerotier_crypto::secret::Secret;
|
||||
use zerotier_utils::hex;
|
||||
use zerotier_utils::ms_monotonic;
|
||||
|
||||
const TEST_MTU: usize = 1500;
|
||||
|
||||
struct TestApplication {
|
||||
identity_key: P384KeyPair,
|
||||
}
|
||||
|
||||
impl zssp::ApplicationLayer for TestApplication {
|
||||
const REKEY_AFTER_USES: u64 = 300000;
|
||||
const EXPIRE_AFTER_USES: u64 = 2147483648;
|
||||
const REKEY_AFTER_TIME_MS: i64 = 1000 * 60 * 60 * 2;
|
||||
const REKEY_AFTER_TIME_MS_MAX_JITTER: u32 = 1000 * 60 * 10;
|
||||
const INCOMING_SESSION_NEGOTIATION_TIMEOUT_MS: i64 = 2000;
|
||||
const RETRY_INTERVAL: i64 = 500;
|
||||
|
||||
type Data = ();
|
||||
type IncomingPacketBuffer = Vec<u8>;
|
||||
type PhysicalPath = usize;
|
||||
|
||||
fn get_local_s_public_blob(&self) -> &[u8] {
|
||||
self.identity_key.public_key_bytes()
|
||||
}
|
||||
|
||||
fn get_local_s_keypair(&self) -> &zerotier_crypto::p384::P384KeyPair {
|
||||
&self.identity_key
|
||||
}
|
||||
}
|
||||
|
||||
fn alice_main(
|
||||
run: &AtomicBool,
|
||||
packet_success_rate: u32,
|
||||
alice_app: &TestApplication,
|
||||
bob_app: &TestApplication,
|
||||
alice_out: mpsc::SyncSender<Vec<u8>>,
|
||||
alice_in: mpsc::Receiver<Vec<u8>>,
|
||||
) {
|
||||
let context = zssp::Context::<TestApplication>::new(16);
|
||||
let mut data_buf = [0u8; 65536];
|
||||
let mut next_service = ms_monotonic() + 500;
|
||||
let mut last_ratchet_count = 0;
|
||||
let test_data = [1u8; TEST_MTU * 10];
|
||||
let mut up = false;
|
||||
let mut last_error = zssp::Error::UnknownProtocolVersion;
|
||||
|
||||
let alice_session = context
|
||||
.open(
|
||||
alice_app,
|
||||
|b| {
|
||||
let _ = alice_out.send(b.to_vec());
|
||||
},
|
||||
TEST_MTU,
|
||||
bob_app.identity_key.public_key(),
|
||||
Secret::default(),
|
||||
None,
|
||||
(),
|
||||
ms_monotonic(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
println!("[alice] opening session {}", alice_session.id.to_string());
|
||||
|
||||
while run.load(Ordering::Relaxed) {
|
||||
let current_time = ms_monotonic();
|
||||
loop {
|
||||
let pkt = alice_in.try_recv();
|
||||
if let Ok(pkt) = pkt {
|
||||
if (random::xorshift64_random() as u32) <= packet_success_rate {
|
||||
match context.receive(
|
||||
alice_app,
|
||||
|| true,
|
||||
|s_public, _| Some((P384PublicKey::from_bytes(s_public).unwrap(), Secret::default(), ())),
|
||||
|_, b| {
|
||||
let _ = alice_out.send(b.to_vec());
|
||||
},
|
||||
&0,
|
||||
&mut data_buf,
|
||||
pkt,
|
||||
TEST_MTU,
|
||||
current_time,
|
||||
) {
|
||||
Ok(zssp::ReceiveResult::Ok(_)) => {
|
||||
//println!("[alice] ok");
|
||||
}
|
||||
Ok(zssp::ReceiveResult::OkData(_, _)) => {
|
||||
//println!("[alice] received {}", data.len());
|
||||
}
|
||||
Ok(zssp::ReceiveResult::OkNewSession(s)) => {
|
||||
println!("[alice] new session {}", s.id.to_string());
|
||||
}
|
||||
Ok(zssp::ReceiveResult::Rejected) => {}
|
||||
Err(e) => {
|
||||
if e != last_error {
|
||||
println!("[alice] ERROR {}", e.to_string());
|
||||
last_error = e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if up {
|
||||
let ki = alice_session.key_info().unwrap();
|
||||
if ki.0 > last_ratchet_count {
|
||||
last_ratchet_count = ki.0;
|
||||
println!("[alice] new key! ratchet count {} fp {}", ki.0, hex::to_string(&ki.1[..16]));
|
||||
}
|
||||
|
||||
assert!(alice_session
|
||||
.send(
|
||||
|b| {
|
||||
let _ = alice_out.send(b.to_vec());
|
||||
},
|
||||
&mut data_buf[..TEST_MTU],
|
||||
&test_data[..1400 + ((random::xorshift64_random() as usize) % (test_data.len() - 1400))],
|
||||
)
|
||||
.is_ok());
|
||||
} else {
|
||||
if alice_session.established() {
|
||||
up = true;
|
||||
} else {
|
||||
thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
}
|
||||
|
||||
if current_time >= next_service {
|
||||
next_service = current_time
|
||||
+ context.service(
|
||||
|_, b| {
|
||||
let _ = alice_out.send(b.to_vec());
|
||||
},
|
||||
TEST_MTU,
|
||||
current_time,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn bob_main(
|
||||
run: &AtomicBool,
|
||||
packet_success_rate: u32,
|
||||
_alice_app: &TestApplication,
|
||||
bob_app: &TestApplication,
|
||||
bob_out: mpsc::SyncSender<Vec<u8>>,
|
||||
bob_in: mpsc::Receiver<Vec<u8>>,
|
||||
) {
|
||||
let context = zssp::Context::<TestApplication>::new(16);
|
||||
let mut data_buf = [0u8; 65536];
|
||||
let mut data_buf_2 = [0u8; TEST_MTU];
|
||||
let mut last_ratchet_count = 0;
|
||||
let mut last_speed_metric = ms_monotonic();
|
||||
let mut next_service = last_speed_metric + 500;
|
||||
let mut transferred = 0u64;
|
||||
let mut last_error = zssp::Error::UnknownProtocolVersion;
|
||||
|
||||
let mut bob_session = None;
|
||||
|
||||
while run.load(Ordering::Relaxed) {
|
||||
let pkt = bob_in.recv_timeout(Duration::from_millis(100));
|
||||
let current_time = ms_monotonic();
|
||||
|
||||
if let Ok(pkt) = pkt {
|
||||
if (random::xorshift64_random() as u32) <= packet_success_rate {
|
||||
match context.receive(
|
||||
bob_app,
|
||||
|| true,
|
||||
|s_public, _| Some((P384PublicKey::from_bytes(s_public).unwrap(), Secret::default(), ())),
|
||||
|_, b| {
|
||||
let _ = bob_out.send(b.to_vec());
|
||||
},
|
||||
&0,
|
||||
&mut data_buf,
|
||||
pkt,
|
||||
TEST_MTU,
|
||||
current_time,
|
||||
) {
|
||||
Ok(zssp::ReceiveResult::Ok(_)) => {
|
||||
//println!("[bob] ok");
|
||||
}
|
||||
Ok(zssp::ReceiveResult::OkData(s, data)) => {
|
||||
//println!("[bob] received {}", data.len());
|
||||
assert!(s
|
||||
.send(
|
||||
|b| {
|
||||
let _ = bob_out.send(b.to_vec());
|
||||
},
|
||||
&mut data_buf_2,
|
||||
data.as_mut(),
|
||||
)
|
||||
.is_ok());
|
||||
transferred += data.len() as u64 * 2; // *2 because we are also sending this many bytes back
|
||||
}
|
||||
Ok(zssp::ReceiveResult::OkNewSession(s)) => {
|
||||
println!("[bob] new session {}", s.id.to_string());
|
||||
let _ = bob_session.replace(s);
|
||||
}
|
||||
Ok(zssp::ReceiveResult::Rejected) => {}
|
||||
Err(e) => {
|
||||
if e != last_error {
|
||||
println!("[bob] ERROR {}", e.to_string());
|
||||
last_error = e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(bob_session) = bob_session.as_ref() {
|
||||
let ki = bob_session.key_info().unwrap();
|
||||
if ki.0 > last_ratchet_count {
|
||||
last_ratchet_count = ki.0;
|
||||
println!("[bob] new key! ratchet count {} fp {}", ki.0, hex::to_string(&ki.1[..16]));
|
||||
}
|
||||
}
|
||||
|
||||
let speed_metric_elapsed = current_time - last_speed_metric;
|
||||
if speed_metric_elapsed >= 1000 {
|
||||
last_speed_metric = current_time;
|
||||
if transferred > 0 {
|
||||
println!(
|
||||
"[bob] throughput: {} MiB/sec (combined input and output)",
|
||||
((transferred as f64) / 1048576.0) / ((speed_metric_elapsed as f64) / 1000.0)
|
||||
);
|
||||
}
|
||||
transferred = 0;
|
||||
}
|
||||
|
||||
if current_time >= next_service {
|
||||
next_service = current_time
|
||||
+ context.service(
|
||||
|_, b| {
|
||||
let _ = bob_out.send(b.to_vec());
|
||||
},
|
||||
TEST_MTU,
|
||||
current_time,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let run = AtomicBool::new(true);
|
||||
|
||||
let alice_app = TestApplication { identity_key: P384KeyPair::generate() };
|
||||
let bob_app = TestApplication { identity_key: P384KeyPair::generate() };
|
||||
|
||||
let (alice_out, bob_in) = mpsc::sync_channel::<Vec<u8>>(1024);
|
||||
let (bob_out, alice_in) = mpsc::sync_channel::<Vec<u8>>(1024);
|
||||
|
||||
let args = std::env::args();
|
||||
let packet_success_rate = if args.len() <= 1 {
|
||||
u32::MAX
|
||||
} else {
|
||||
((u32::MAX as f64) * f64::from_str(args.last().unwrap().as_str()).unwrap()) as u32
|
||||
};
|
||||
|
||||
thread::scope(|ts| {
|
||||
let alice_thread = ts.spawn(|| alice_main(&run, packet_success_rate, &alice_app, &bob_app, alice_out, alice_in));
|
||||
let bob_thread = ts.spawn(|| bob_main(&run, packet_success_rate, &alice_app, &bob_app, bob_out, bob_in));
|
||||
|
||||
thread::sleep(Duration::from_secs(60 * 60));
|
||||
|
||||
run.store(false, Ordering::SeqCst);
|
||||
let _ = alice_thread.join();
|
||||
let _ = bob_thread.join();
|
||||
});
|
||||
|
||||
std::process::exit(0);
|
||||
}
|
219
zssp/src/proto.rs
Normal file
219
zssp/src/proto.rs
Normal file
|
@ -0,0 +1,219 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* (c) ZeroTier, Inc.
|
||||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
use std::mem::size_of;
|
||||
|
||||
use pqc_kyber::{KYBER_CIPHERTEXTBYTES, KYBER_PUBLICKEYBYTES};
|
||||
use zerotier_crypto::hash::{HMAC_SHA384_SIZE, SHA384_HASH_SIZE};
|
||||
use zerotier_crypto::p384::P384_PUBLIC_KEY_SIZE;
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::sessionid::SessionId;
|
||||
|
||||
/// Minimum size of a valid physical ZSSP packet of any type. Anything smaller is discarded.
|
||||
pub const MIN_PACKET_SIZE: usize = HEADER_SIZE + AES_GCM_TAG_SIZE;
|
||||
|
||||
/// Minimum physical MTU for ZSSP to function.
|
||||
pub const MIN_TRANSPORT_MTU: usize = 128;
|
||||
|
||||
/// Maximum combined size of static public blob and metadata.
|
||||
pub const MAX_INIT_PAYLOAD_SIZE: usize = MAX_NOISE_HANDSHAKE_SIZE - ALICE_NOISE_XK_ACK_MIN_SIZE;
|
||||
|
||||
/// Version 0: Noise_XK with NIST P-384 plus Kyber1024 hybrid exchange on session init.
|
||||
pub(crate) const SESSION_PROTOCOL_VERSION: u8 = 0x00;
|
||||
|
||||
/// Maximum window over which packets may be reordered.
|
||||
pub(crate) const COUNTER_WINDOW_MAX_OOO: usize = 32;
|
||||
|
||||
/// Maximum number of counter steps that the counter is allowed to skip ahead.
|
||||
pub(crate) const COUNTER_WINDOW_MAX_SKIP_AHEAD: u64 = 16777216;
|
||||
|
||||
pub(crate) const PACKET_TYPE_NOP: u8 = 0;
|
||||
pub(crate) const PACKET_TYPE_DATA: u8 = 1;
|
||||
pub(crate) const PACKET_TYPE_ALICE_NOISE_XK_INIT: u8 = 2;
|
||||
pub(crate) const PACKET_TYPE_BOB_NOISE_XK_ACK: u8 = 3;
|
||||
pub(crate) const PACKET_TYPE_ALICE_NOISE_XK_ACK: u8 = 4;
|
||||
pub(crate) const PACKET_TYPE_REKEY_INIT: u8 = 5;
|
||||
pub(crate) const PACKET_TYPE_REKEY_ACK: u8 = 6;
|
||||
|
||||
pub(crate) const HEADER_SIZE: usize = 16;
|
||||
pub(crate) const HEADER_PROTECT_ENCRYPT_START: usize = 6;
|
||||
pub(crate) const HEADER_PROTECT_ENCRYPT_END: usize = 22;
|
||||
|
||||
pub(crate) const KBKDF_KEY_USAGE_LABEL_INIT_ENCRYPTION: u8 = b'x'; // AES-CTR encryption during initial setup
|
||||
pub(crate) const KBKDF_KEY_USAGE_LABEL_INIT_AUTHENTICATION: u8 = b'X'; // HMAC-SHA384 during initial setup
|
||||
pub(crate) const KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB: u8 = b'A'; // AES-GCM in A->B direction
|
||||
pub(crate) const KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE: u8 = b'B'; // AES-GCM in B->A direction
|
||||
pub(crate) const KBKDF_KEY_USAGE_LABEL_RATCHET: u8 = b'R'; // Key used in derivatin of next session key
|
||||
|
||||
pub(crate) const MAX_FRAGMENTS: usize = 48; // hard protocol max: 63
|
||||
pub(crate) const MAX_NOISE_HANDSHAKE_FRAGMENTS: usize = 16; // enough room for p384 + ZT identity + kyber1024 + tag/hmac/etc.
|
||||
pub(crate) const MAX_NOISE_HANDSHAKE_SIZE: usize = MAX_NOISE_HANDSHAKE_FRAGMENTS * MIN_TRANSPORT_MTU;
|
||||
|
||||
/// Size of keys used during derivation, mixing, etc. process.
|
||||
pub(crate) const BASE_KEY_SIZE: usize = 64;
|
||||
|
||||
pub(crate) const AES_256_KEY_SIZE: usize = 32;
|
||||
pub(crate) const AES_HEADER_PROTECTION_KEY_SIZE: usize = 16;
|
||||
pub(crate) const AES_GCM_TAG_SIZE: usize = 16;
|
||||
pub(crate) const AES_GCM_NONCE_SIZE: usize = 12;
|
||||
|
||||
/// The first packet in Noise_XK exchange containing Alice's ephemeral keys, session ID, and a random
|
||||
/// symmetric key to protect header fragmentation fields for this session.
|
||||
#[allow(unused)]
|
||||
#[repr(C, packed)]
|
||||
pub(crate) struct AliceNoiseXKInit {
|
||||
pub header: [u8; HEADER_SIZE],
|
||||
pub session_protocol_version: u8,
|
||||
pub alice_noise_e: [u8; P384_PUBLIC_KEY_SIZE],
|
||||
// -- start AES-CTR(es) encrypted section
|
||||
pub alice_session_id: [u8; SessionId::SIZE],
|
||||
pub alice_hk_public: [u8; KYBER_PUBLICKEYBYTES],
|
||||
pub header_protection_key: [u8; AES_HEADER_PROTECTION_KEY_SIZE],
|
||||
// -- end encrypted section
|
||||
pub hmac_es: [u8; HMAC_SHA384_SIZE],
|
||||
}
|
||||
|
||||
impl AliceNoiseXKInit {
|
||||
pub const ENC_START: usize = HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE;
|
||||
pub const AUTH_START: usize = Self::ENC_START + SessionId::SIZE + KYBER_PUBLICKEYBYTES + AES_HEADER_PROTECTION_KEY_SIZE;
|
||||
pub const SIZE: usize = Self::AUTH_START + HMAC_SHA384_SIZE;
|
||||
}
|
||||
|
||||
/// The response to AliceNoiceXKInit containing Bob's ephemeral keys.
|
||||
#[allow(unused)]
|
||||
#[repr(C, packed)]
|
||||
pub(crate) struct BobNoiseXKAck {
|
||||
pub header: [u8; HEADER_SIZE],
|
||||
pub session_protocol_version: u8,
|
||||
pub bob_noise_e: [u8; P384_PUBLIC_KEY_SIZE],
|
||||
// -- start AES-CTR(es_ee) encrypted section
|
||||
pub bob_session_id: [u8; SessionId::SIZE],
|
||||
pub bob_hk_ciphertext: [u8; KYBER_CIPHERTEXTBYTES],
|
||||
// -- end encrypted sectiion
|
||||
pub hmac_es_ee: [u8; HMAC_SHA384_SIZE],
|
||||
}
|
||||
|
||||
impl BobNoiseXKAck {
|
||||
pub const ENC_START: usize = HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE;
|
||||
pub const AUTH_START: usize = Self::ENC_START + SessionId::SIZE + KYBER_CIPHERTEXTBYTES;
|
||||
pub const SIZE: usize = Self::AUTH_START + HMAC_SHA384_SIZE;
|
||||
}
|
||||
|
||||
/// Alice's final response containing her identity (she already knows Bob's) and meta-data.
|
||||
/*
|
||||
#[allow(unused)]
|
||||
#[repr(C, packed)]
|
||||
pub(crate) struct AliceNoiseXKAck {
|
||||
pub header: [u8; HEADER_SIZE],
|
||||
pub session_protocol_version: u8,
|
||||
// -- start AES-CTR(es_ee_hk) encrypted section
|
||||
pub alice_static_blob_length: [u8; 2],
|
||||
pub alice_static_blob: [u8; ???],
|
||||
pub alice_metadata_length: [u8; 2],
|
||||
pub alice_metadata: [u8; ???],
|
||||
// -- end encrypted section
|
||||
pub hmac_es_ee: [u8; HMAC_SHA384_SIZE],
|
||||
pub hmac_es_ee_se_hk_psk: [u8; HMAC_SHA384_SIZE],
|
||||
}
|
||||
*/
|
||||
|
||||
pub(crate) const ALICE_NOISE_XK_ACK_ENC_START: usize = HEADER_SIZE + 1;
|
||||
pub(crate) const ALICE_NOISE_XK_ACK_AUTH_SIZE: usize = HMAC_SHA384_SIZE + HMAC_SHA384_SIZE;
|
||||
pub(crate) const ALICE_NOISE_XK_ACK_MIN_SIZE: usize = ALICE_NOISE_XK_ACK_ENC_START + 2 + 2 + ALICE_NOISE_XK_ACK_AUTH_SIZE;
|
||||
|
||||
#[allow(unused)]
|
||||
#[repr(C, packed)]
|
||||
pub(crate) struct RekeyInit {
|
||||
pub header: [u8; HEADER_SIZE],
|
||||
pub session_protocol_version: u8,
|
||||
// -- start AES-GCM encrypted portion (using current key)
|
||||
pub alice_e: [u8; P384_PUBLIC_KEY_SIZE],
|
||||
// -- end AES-GCM encrypted portion
|
||||
pub gcm_mac: [u8; AES_GCM_TAG_SIZE],
|
||||
}
|
||||
|
||||
impl RekeyInit {
|
||||
pub const ENC_START: usize = HEADER_SIZE + 1;
|
||||
pub const AUTH_START: usize = Self::ENC_START + P384_PUBLIC_KEY_SIZE;
|
||||
pub const SIZE: usize = Self::AUTH_START + AES_GCM_TAG_SIZE;
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[repr(C, packed)]
|
||||
pub(crate) struct RekeyAck {
|
||||
pub header: [u8; HEADER_SIZE],
|
||||
pub session_protocol_version: u8,
|
||||
// -- start AES-GCM encrypted portion (using current key)
|
||||
pub bob_e: [u8; P384_PUBLIC_KEY_SIZE],
|
||||
pub next_key_fingerprint: [u8; SHA384_HASH_SIZE],
|
||||
// -- end AES-GCM encrypted portion
|
||||
pub gcm_mac: [u8; AES_GCM_TAG_SIZE],
|
||||
}
|
||||
|
||||
impl RekeyAck {
|
||||
pub const ENC_START: usize = HEADER_SIZE + 1;
|
||||
pub const AUTH_START: usize = Self::ENC_START + P384_PUBLIC_KEY_SIZE + SHA384_HASH_SIZE;
|
||||
pub const SIZE: usize = Self::AUTH_START + AES_GCM_TAG_SIZE;
|
||||
}
|
||||
|
||||
// Annotate only these structs as being compatible with packet_buffer_as_bytes(). These structs
|
||||
// are packed flat buffers containing only byte or byte array fields, making them safe to treat
|
||||
// this way even on architectures that require type size aligned access.
|
||||
pub(crate) trait ProtocolFlatBuffer {}
|
||||
impl ProtocolFlatBuffer for AliceNoiseXKInit {}
|
||||
impl ProtocolFlatBuffer for BobNoiseXKAck {}
|
||||
impl ProtocolFlatBuffer for RekeyInit {}
|
||||
impl ProtocolFlatBuffer for RekeyAck {}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C, packed)]
|
||||
struct MessageNonceCreateBuffer(u64, u32);
|
||||
|
||||
/// Create a 96-bit AES-GCM nonce.
|
||||
///
|
||||
/// The primary information that we want to be contained here is the counter and the
|
||||
/// packet type. The former makes this unique and the latter's inclusion authenticates
|
||||
/// it as effectively AAD. Other elements of the header are either not authenticated,
|
||||
/// like fragmentation info, or their authentication is implied via key exchange like
|
||||
/// the session ID.
|
||||
///
|
||||
/// This is also used as part of HMAC authentication for key exchange packets.
|
||||
#[inline(always)]
|
||||
pub(crate) fn create_message_nonce(packet_type: u8, counter: u64) -> [u8; AES_GCM_NONCE_SIZE] {
|
||||
unsafe { std::mem::transmute(MessageNonceCreateBuffer(counter.to_le(), (packet_type as u32).to_le())) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn byte_array_as_proto_buffer<B: ProtocolFlatBuffer>(b: &[u8]) -> Result<&B, Error> {
|
||||
if b.len() >= size_of::<B>() {
|
||||
Ok(unsafe { &*b.as_ptr().cast() })
|
||||
} else {
|
||||
Err(Error::InvalidPacket)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn byte_array_as_proto_buffer_mut<B: ProtocolFlatBuffer>(b: &mut [u8]) -> Result<&mut B, Error> {
|
||||
if b.len() >= size_of::<B>() {
|
||||
Ok(unsafe { &mut *b.as_mut_ptr().cast() })
|
||||
} else {
|
||||
Err(Error::InvalidPacket)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn check_packed_struct_sizing() {
|
||||
assert_eq!(size_of::<AliceNoiseXKInit>(), AliceNoiseXKInit::SIZE);
|
||||
assert_eq!(size_of::<BobNoiseXKAck>(), BobNoiseXKAck::SIZE);
|
||||
}
|
||||
}
|
|
@ -1,17 +1,27 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* (c) ZeroTier, Inc.
|
||||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
use std::fmt::Display;
|
||||
use std::num::NonZeroU64;
|
||||
|
||||
use zerotier_crypto::random;
|
||||
use zerotier_utils::memory::{array_range, as_byte_array};
|
||||
|
||||
use crate::constants::SESSION_ID_SIZE;
|
||||
|
||||
/// 48-bit session ID (most significant 16 bits of u64 are unused)
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct SessionId(NonZeroU64); // stored little endian internally
|
||||
|
||||
const SESSION_ID_SIZE_BYTES: usize = 6;
|
||||
|
||||
impl SessionId {
|
||||
pub const SIZE: usize = SESSION_ID_SIZE_BYTES;
|
||||
pub const NONE: u64 = 0;
|
||||
pub const MAX: u64 = 0xffffffffffff;
|
||||
|
||||
/// Create a new session ID, panicing if 'i' is zero or exceeds MAX.
|
||||
|
@ -20,11 +30,18 @@ impl SessionId {
|
|||
Self(NonZeroU64::new(i.to_le()).unwrap())
|
||||
}
|
||||
|
||||
/// Create a new random session ID (non-cryptographic PRNG)
|
||||
/// Create a new random (non-zero) session ID (non-cryptographic PRNG)
|
||||
pub fn random() -> Self {
|
||||
Self(NonZeroU64::new(((random::xorshift64_random() % (Self::MAX - 1)) + 1).to_le()).unwrap())
|
||||
}
|
||||
|
||||
pub(crate) fn new_from_bytes(b: &[u8; Self::SIZE]) -> Option<SessionId> {
|
||||
let mut tmp = [0u8; 8];
|
||||
tmp[..SESSION_ID_SIZE_BYTES].copy_from_slice(b);
|
||||
Self::new_from_u64_le(u64::from_ne_bytes(tmp))
|
||||
}
|
||||
|
||||
/// Create from a u64 that is already in little-endian byte order.
|
||||
#[inline(always)]
|
||||
pub(crate) fn new_from_u64_le(i: u64) -> Option<SessionId> {
|
||||
NonZeroU64::new(i & Self::MAX.to_le()).map(|i| Self(i))
|
||||
|
@ -32,8 +49,8 @@ impl SessionId {
|
|||
|
||||
/// Get this session ID as a little-endian byte array.
|
||||
#[inline(always)]
|
||||
pub(crate) fn as_bytes(&self) -> &[u8; SESSION_ID_SIZE] {
|
||||
array_range::<u8, 8, 0, SESSION_ID_SIZE>(as_byte_array(&self.0))
|
||||
pub(crate) fn as_bytes(&self) -> &[u8; Self::SIZE] {
|
||||
array_range::<u8, 8, 0, SESSION_ID_SIZE_BYTES>(as_byte_array(&self.0))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,216 +0,0 @@
|
|||
#[allow(unused_imports)]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::LinkedList;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use zerotier_crypto::hash::SHA384;
|
||||
use zerotier_crypto::p384::{P384KeyPair, P384PublicKey};
|
||||
use zerotier_crypto::random;
|
||||
use zerotier_crypto::secret::Secret;
|
||||
use zerotier_utils::hex;
|
||||
|
||||
use crate::*;
|
||||
use constants::*;
|
||||
|
||||
struct TestHost {
|
||||
local_s: P384KeyPair,
|
||||
local_s_hash: [u8; 48],
|
||||
psk: Secret<64>,
|
||||
session: Mutex<Option<Arc<Session<Box<TestHost>>>>>,
|
||||
session_id_counter: Mutex<u64>,
|
||||
queue: Mutex<LinkedList<Vec<u8>>>,
|
||||
key_id: Mutex<[u8; 16]>,
|
||||
this_name: &'static str,
|
||||
other_name: &'static str,
|
||||
}
|
||||
|
||||
impl TestHost {
|
||||
fn new(psk: Secret<64>, this_name: &'static str, other_name: &'static str) -> Self {
|
||||
let local_s = P384KeyPair::generate();
|
||||
let local_s_hash = SHA384::hash(local_s.public_key_bytes());
|
||||
Self {
|
||||
local_s,
|
||||
local_s_hash,
|
||||
psk,
|
||||
session: Mutex::new(None),
|
||||
session_id_counter: Mutex::new(1),
|
||||
queue: Mutex::new(LinkedList::new()),
|
||||
key_id: Mutex::new([0; 16]),
|
||||
this_name,
|
||||
other_name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ApplicationLayer for Box<TestHost> {
|
||||
type Data = u32;
|
||||
type SessionRef<'a> = Arc<Session<Box<TestHost>>>;
|
||||
type IncomingPacketBuffer = Vec<u8>;
|
||||
type RemoteAddress = u32;
|
||||
|
||||
const REKEY_RATE_LIMIT_MS: i64 = 0;
|
||||
|
||||
fn get_local_s_public_blob(&self) -> &[u8] {
|
||||
self.local_s.public_key_bytes()
|
||||
}
|
||||
|
||||
fn get_local_s_public_blob_hash(&self) -> &[u8; 48] {
|
||||
&self.local_s_hash
|
||||
}
|
||||
|
||||
fn get_local_s_keypair(&self) -> &P384KeyPair {
|
||||
&self.local_s
|
||||
}
|
||||
|
||||
fn extract_s_public_from_raw(static_public: &[u8]) -> Option<P384PublicKey> {
|
||||
P384PublicKey::from_bytes(static_public)
|
||||
}
|
||||
|
||||
fn lookup_session<'a>(&self, local_session_id: SessionId) -> Option<Self::SessionRef<'a>> {
|
||||
self.session.lock().unwrap().as_ref().and_then(|s| {
|
||||
if s.id == local_session_id {
|
||||
Some(s.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn check_new_session(&self, _: &ReceiveContext<Self>, _: &Self::RemoteAddress) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn accept_new_session(&self, _: &ReceiveContext<Self>, _: &u32, _: &[u8], _: &[u8]) -> Option<(SessionId, Secret<64>, Self::Data)> {
|
||||
loop {
|
||||
let mut new_id = self.session_id_counter.lock().unwrap();
|
||||
*new_id += 1;
|
||||
return Some((SessionId::new_from_u64_le((*new_id).to_le()).unwrap(), self.psk.clone(), 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[test]
|
||||
fn establish_session() {
|
||||
let mut data_buf = [0_u8; (1280 - 32) * MAX_FRAGMENTS];
|
||||
let mut mtu_buffer = [0_u8; 1280];
|
||||
let mut psk: Secret<64> = Secret::default();
|
||||
random::fill_bytes_secure(&mut psk.0);
|
||||
|
||||
let alice_host = Box::new(TestHost::new(psk.clone(), "alice", "bob"));
|
||||
let bob_host = Box::new(TestHost::new(psk.clone(), "bob", "alice"));
|
||||
let alice_rc: Box<ReceiveContext<Box<TestHost>>> = Box::new(ReceiveContext::new(&alice_host));
|
||||
let bob_rc: Box<ReceiveContext<Box<TestHost>>> = Box::new(ReceiveContext::new(&bob_host));
|
||||
|
||||
//println!("zssp: size of session (bytes): {}", std::mem::size_of::<Session<Box<TestHost>>>());
|
||||
|
||||
let _ = alice_host.session.lock().unwrap().insert(Arc::new(
|
||||
Session::start_new(
|
||||
&alice_host,
|
||||
|data| bob_host.queue.lock().unwrap().push_front(data.to_vec()),
|
||||
SessionId::random(),
|
||||
bob_host.local_s.public_key_bytes(),
|
||||
&[],
|
||||
&psk,
|
||||
1,
|
||||
mtu_buffer.len(),
|
||||
1,
|
||||
)
|
||||
.unwrap(),
|
||||
));
|
||||
|
||||
for test_loop in 0..256 {
|
||||
let time_ticks = (test_loop * 10000) as i64;
|
||||
for host in [&alice_host, &bob_host] {
|
||||
let send_to_other = |data: &mut [u8]| {
|
||||
if std::ptr::eq(host, &alice_host) {
|
||||
bob_host.queue.lock().unwrap().push_front(data.to_vec());
|
||||
} else {
|
||||
alice_host.queue.lock().unwrap().push_front(data.to_vec());
|
||||
}
|
||||
};
|
||||
|
||||
let rc = if std::ptr::eq(host, &alice_host) {
|
||||
&alice_rc
|
||||
} else {
|
||||
&bob_rc
|
||||
};
|
||||
|
||||
loop {
|
||||
if let Some(qi) = host.queue.lock().unwrap().pop_back() {
|
||||
let qi_len = qi.len();
|
||||
let r = rc.receive(host, &0, send_to_other, &mut data_buf, qi, mtu_buffer.len(), time_ticks);
|
||||
if r.is_ok() {
|
||||
let r = r.unwrap();
|
||||
match r {
|
||||
ReceiveResult::Ok => {
|
||||
//println!("zssp: {} => {} ({}): Ok", host.other_name, host.this_name, qi_len);
|
||||
}
|
||||
ReceiveResult::OkData(data) => {
|
||||
//println!("zssp: {} => {} ({}): OkData length=={}", host.other_name, host.this_name, qi_len, data.len());
|
||||
assert!(!data.iter().any(|x| *x != 0x12));
|
||||
}
|
||||
ReceiveResult::OkNewSession(new_session) => {
|
||||
println!("zssp: new session at {} ({})", host.this_name, u64::from(new_session.id));
|
||||
let mut hs = host.session.lock().unwrap();
|
||||
assert!(hs.is_none());
|
||||
let _ = hs.insert(Arc::new(new_session));
|
||||
}
|
||||
ReceiveResult::Ignored => {
|
||||
println!("zssp: {} => {} ({}): Ignored", host.other_name, host.this_name, qi_len);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!(
|
||||
"zssp: {} => {} ({}): error: {}",
|
||||
host.other_name,
|
||||
host.this_name,
|
||||
qi_len,
|
||||
r.err().unwrap().to_string()
|
||||
);
|
||||
panic!();
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
data_buf.fill(0x12);
|
||||
if let Some(session) = host.session.lock().unwrap().as_ref().cloned() {
|
||||
if session.established() {
|
||||
{
|
||||
let mut key_id = host.key_id.lock().unwrap();
|
||||
let security_info = session.status().unwrap();
|
||||
if !security_info.0.eq(key_id.as_ref()) {
|
||||
*key_id = security_info.0;
|
||||
println!(
|
||||
"zssp: new key at {}: fingerprint {} ratchet {} kyber {} latest role {}",
|
||||
host.this_name,
|
||||
hex::to_string(key_id.as_ref()),
|
||||
security_info.1,
|
||||
security_info.3,
|
||||
match security_info.2 {
|
||||
Role::Alice => "A",
|
||||
Role::Bob => "B",
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
for _ in 0..4 {
|
||||
assert!(session
|
||||
.send(
|
||||
send_to_other,
|
||||
&mut mtu_buffer,
|
||||
&data_buf[..((random::xorshift64_random() as usize) % data_buf.len())]
|
||||
)
|
||||
.is_ok());
|
||||
}
|
||||
if (test_loop % 13) == 0 && test_loop > 0 {
|
||||
session.service(host, send_to_other, &[], mtu_buffer.len(), time_ticks, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
2504
zssp/src/zssp.rs
2504
zssp/src/zssp.rs
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue