mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-06-07 21:13:44 +02:00
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>
This commit is contained in:
parent
52234c44fb
commit
5cf99ecb1d
38 changed files with 2421 additions and 1505 deletions
|
@ -1,34 +1,40 @@
|
|||
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("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;
|
||||
|
@ -183,6 +183,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 {
|
||||
|
@ -307,6 +402,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 +496,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 +632,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 {
|
||||
|
|
|
@ -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()) };
|
||||
}
|
||||
|
|
141
crypto/src/mimcvdf.rs
Normal file
141
crypto/src/mimcvdf.rs
Normal file
|
@ -0,0 +1,141 @@
|
|||
/* 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 hash function 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 also nice because it's incredibly simple with a tiny code footprint.
|
||||
*
|
||||
* 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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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] {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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};
|
||||
|
||||
|
|
|
@ -5,6 +5,22 @@ 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" }
|
||||
|
|
|
@ -1,77 +1,78 @@
|
|||
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 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 = 2147483648;
|
||||
|
||||
/// 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;
|
||||
|
||||
/// Get a reference to this host's static public key blob.
|
||||
///
|
||||
/// This must contain a NIST P-384 public key but can contain other information. In ZeroTier this
|
||||
/// 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,14 @@
|
|||
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/
|
||||
*/
|
||||
|
||||
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 +21,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 +36,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 +46,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",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 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};
|
||||
|
|
219
zssp/src/main.rs
Normal file
219
zssp/src/main.rs
Normal file
|
@ -0,0 +1,219 @@
|
|||
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::secret::Secret;
|
||||
use zerotier_utils::ms_monotonic;
|
||||
|
||||
const TEST_MTU: usize = 1500;
|
||||
|
||||
struct TestApplication {
|
||||
identity_key: P384KeyPair,
|
||||
}
|
||||
|
||||
impl zssp::ApplicationLayer for TestApplication {
|
||||
type Data = ();
|
||||
|
||||
type IncomingPacketBuffer = Vec<u8>;
|
||||
|
||||
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,
|
||||
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 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());
|
||||
|
||||
let test_data = [1u8; 10000];
|
||||
let mut up = false;
|
||||
|
||||
while run.load(Ordering::Relaxed) {
|
||||
let pkt = alice_in.try_recv();
|
||||
let current_time = ms_monotonic();
|
||||
|
||||
if let Ok(pkt) = pkt {
|
||||
//println!("bob >> alice {}", pkt.len());
|
||||
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());
|
||||
},
|
||||
&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) => {
|
||||
println!("[alice] ERROR {}", e.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if up {
|
||||
assert!(alice_session
|
||||
.send(
|
||||
|b| {
|
||||
let _ = alice_out.send(b.to_vec());
|
||||
},
|
||||
&mut data_buf[..TEST_MTU],
|
||||
&test_data[..2048 + ((zerotier_crypto::random::xorshift64_random() as usize) % (test_data.len() - 2048))],
|
||||
)
|
||||
.is_ok());
|
||||
} else {
|
||||
if alice_session.established() {
|
||||
up = true;
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
_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 last_speed_metric = ms_monotonic();
|
||||
let mut next_service = last_speed_metric + 500;
|
||||
let mut transferred = 0u64;
|
||||
|
||||
let mut bob_session = None;
|
||||
|
||||
while run.load(Ordering::Relaxed) {
|
||||
let pkt = bob_in.recv_timeout(Duration::from_millis(10));
|
||||
let current_time = ms_monotonic();
|
||||
|
||||
if let Ok(pkt) = pkt {
|
||||
//println!("alice >> bob {}", pkt.len());
|
||||
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());
|
||||
},
|
||||
&mut data_buf,
|
||||
pkt,
|
||||
TEST_MTU,
|
||||
current_time,
|
||||
) {
|
||||
Ok(zssp::ReceiveResult::Ok) => {
|
||||
//println!("[bob] ok");
|
||||
}
|
||||
Ok(zssp::ReceiveResult::OkData(_, data)) => {
|
||||
//println!("[bob] received {}", data.len());
|
||||
transferred += data.len() as u64;
|
||||
}
|
||||
Ok(zssp::ReceiveResult::OkNewSession(s)) => {
|
||||
println!("[bob] new session {}", s.id.to_string());
|
||||
let _ = bob_session.replace(s);
|
||||
}
|
||||
Ok(zssp::ReceiveResult::Rejected) => {}
|
||||
Err(e) => {
|
||||
println!("[bob] ERROR {}", e.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let speed_metric_elapsed = current_time - last_speed_metric;
|
||||
if speed_metric_elapsed >= 1000 {
|
||||
last_speed_metric = current_time;
|
||||
println!(
|
||||
"[bob] RX speed {} MiB/sec",
|
||||
((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>>(128);
|
||||
let (bob_out, alice_in) = mpsc::sync_channel::<Vec<u8>>(128);
|
||||
|
||||
thread::scope(|ts| {
|
||||
let alice_thread = ts.spawn(|| alice_main(&run, &alice_app, &bob_app, alice_out, alice_in));
|
||||
let bob_thread = ts.spawn(|| bob_main(&run, &alice_app, &bob_app, bob_out, bob_in));
|
||||
|
||||
thread::sleep(Duration::from_secs(60 * 10));
|
||||
|
||||
run.store(false, Ordering::SeqCst);
|
||||
let _ = alice_thread.join();
|
||||
let _ = bob_thread.join();
|
||||
});
|
||||
|
||||
std::process::exit(0);
|
||||
}
|
212
zssp/src/proto.rs
Normal file
212
zssp/src/proto.rs
Normal file
|
@ -0,0 +1,212 @@
|
|||
/* 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;
|
||||
|
||||
pub(crate) const SESSION_PROTOCOL_VERSION: u8 = 0x00;
|
||||
|
||||
pub(crate) const COUNTER_WINDOW_MAX_OOO: usize = 16;
|
||||
pub(crate) const COUNTER_WINDOW_MAX_SKIP_AHEAD: u64 = 16777216;
|
||||
|
||||
pub(crate) const PACKET_TYPE_DATA: u8 = 0;
|
||||
pub(crate) const PACKET_TYPE_ALICE_NOISE_XK_INIT: u8 = 1;
|
||||
pub(crate) const PACKET_TYPE_BOB_NOISE_XK_ACK: u8 = 2;
|
||||
pub(crate) const PACKET_TYPE_ALICE_NOISE_XK_ACK: u8 = 3;
|
||||
pub(crate) const PACKET_TYPE_ALICE_REKEY_INIT: u8 = 4;
|
||||
pub(crate) const PACKET_TYPE_BOB_REKEY_ACK: u8 = 5;
|
||||
|
||||
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_KEX_ENCRYPTION: u8 = b'X'; // intermediate keys used in key exchanges
|
||||
pub(crate) const KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION: u8 = b'x'; // intermediate keys used in key exchanges
|
||||
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;
|
||||
|
||||
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 AliceRekeyInit {
|
||||
pub header: [u8; HEADER_SIZE],
|
||||
// -- 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 AliceRekeyInit {
|
||||
pub const ENC_START: usize = HEADER_SIZE;
|
||||
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 BobRekeyAck {
|
||||
pub header: [u8; HEADER_SIZE],
|
||||
// -- 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 BobRekeyAck {
|
||||
pub const ENC_START: usize = HEADER_SIZE;
|
||||
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 NoiseXKAliceStaticAck {}
|
||||
impl ProtocolFlatBuffer for AliceRekeyInit {}
|
||||
impl ProtocolFlatBuffer for BobRekeyAck {}
|
||||
|
||||
#[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,3 +1,12 @@
|
|||
/* 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)]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
@ -214,3 +223,4 @@ mod tests {
|
|||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
2620
zssp/src/zssp.rs
2620
zssp/src/zssp.rs
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue