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:
Adam Ierymenko 2023-02-28 17:54:08 -05:00 committed by GitHub
parent 52234c44fb
commit 5cf99ecb1d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 2421 additions and 1505 deletions

View file

@ -1,34 +1,40 @@
use criterion::{criterion_group, criterion_main, Criterion}; use criterion::{criterion_group, criterion_main, Criterion};
use std::time::Duration; use std::time::Duration;
use zerotier_crypto::mimcvdf;
use zerotier_crypto::p384::*; use zerotier_crypto::p384::*;
use zerotier_crypto::random;
use zerotier_crypto::x25519::*; use zerotier_crypto::x25519::*;
pub fn criterion_benchmark(c: &mut Criterion) { 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_a = P384KeyPair::generate();
let p384_b = 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_a = X25519KeyPair::generate();
let x25519_b = X25519KeyPair::generate(); let x25519_b = X25519KeyPair::generate();
let x25519_b_pub = x25519_b.public_bytes(); let x25519_b_pub = x25519_b.public_bytes();
let mut group = c.benchmark_group("cryptography");
group.measurement_time(Duration::new(10, 0)); group.measurement_time(Duration::new(10, 0));
group.bench_function("ecdhp384", |b| { group.bench_function("ecdhp384", |b| {
b.iter(|| p384_a.agree(p384_b.public_key()).expect("ecdhp384 failed")) 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("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(); group.finish();
} }

View file

@ -46,7 +46,7 @@ mod fruit_flavored {
data_out_len: usize, data_out_len: usize,
data_out_written: *mut usize, data_out_written: *mut usize,
) -> i32; ) -> 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 CCCryptorRelease(cryptor_ref: *mut c_void) -> i32;
fn CCCryptorGCMSetIV(cryptor_ref: *mut c_void, iv: *const c_void, iv_len: usize) -> 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; 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 Send for Aes {}
unsafe impl Sync 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); pub struct AesGcm(*mut c_void, bool);
impl Drop for AesGcm { impl Drop for AesGcm {
@ -307,6 +402,17 @@ mod openssl_aes {
use std::cell::UnsafeCell; use std::cell::UnsafeCell;
use std::mem::MaybeUninit; 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 { fn aes_gcm_by_key_size(ks: usize) -> Cipher {
match ks { match ks {
16 => Cipher::aes_128_gcm(), 16 => Cipher::aes_128_gcm(),
@ -390,6 +496,53 @@ mod openssl_aes {
unsafe impl Send for Aes {} unsafe impl Send for Aes {}
unsafe impl Sync 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); pub struct AesGcm(Secret<32>, usize, CipherCtx, bool);
impl AesGcm { impl AesGcm {
@ -479,10 +632,10 @@ mod openssl_aes {
} }
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
pub use fruit_flavored::{Aes, AesGcm}; pub use fruit_flavored::{Aes, AesCtr, AesGcm};
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
pub use openssl_aes::{Aes, AesGcm}; pub use openssl_aes::{Aes, AesCtr, AesGcm};
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {

View file

@ -7,6 +7,8 @@ use std::ptr::null;
pub const SHA512_HASH_SIZE: usize = 64; pub const SHA512_HASH_SIZE: usize = 64;
pub const SHA384_HASH_SIZE: usize = 48; 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>); pub struct SHA512(Option<openssl::sha::Sha512>);

View file

@ -3,6 +3,7 @@
pub mod aes; pub mod aes;
pub mod aes_gmac_siv; pub mod aes_gmac_siv;
pub mod hash; pub mod hash;
pub mod mimcvdf;
pub mod p384; pub mod p384;
pub mod poly1305; pub mod poly1305;
pub mod random; pub mod random;
@ -27,3 +28,13 @@ pub fn secure_eq<A: AsRef<[u8]> + ?Sized, B: AsRef<[u8]> + ?Sized>(a: &A, b: &B)
false 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
View 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));
}
}
}

View file

@ -37,6 +37,11 @@ impl<const L: usize> Secret<L> {
&self.0 &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. /// Get the first N bytes of this secret as a fixed length array.
#[inline(always)] #[inline(always)]
pub fn first_n<const N: usize>(&self) -> &[u8; N] { pub fn first_n<const N: usize>(&self) -> &[u8; N] {

View file

@ -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::fmt::Debug;
use std::io::Write; use std::io::Write;

View file

@ -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::fmt::Debug;

View file

@ -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::error::Error;
use std::fmt::{Debug, Display}; use std::fmt::{Debug, Display};

View file

@ -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>); struct Defer<F: FnOnce()>(Option<F>);
impl<F: FnOnce()> Drop for Defer<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. /// 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 { pub fn defer<F: FnOnce()>(f: F) -> impl Drop {
Defer(Some(f)) Defer(Some(f))
} }

View file

@ -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::collections::BTreeMap;
use std::io::Write; use std::io::Write;

View file

@ -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::error::Error;
use std::fmt::{Debug, Display}; use std::fmt::{Debug, Display};

View file

@ -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. // These were taken from BSD sysexits.h to provide some standard for process exit codes.

View file

@ -1,6 +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
//use std::sync::atomic::{AtomicI64, Ordering}; * 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. /// Boolean rate limiter with normal (non-atomic) semantics.
#[repr(transparent)] #[repr(transparent)]

View file

@ -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::mem::{size_of, MaybeUninit};
use std::ptr::copy_nonoverlapping; use std::ptr::copy_nonoverlapping;

View file

@ -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] = [ 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', 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',

View file

@ -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::fs::File;
use std::io::Read; use std::io::Read;

View file

@ -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::de::DeserializeOwned;
use serde::Serialize; use serde::Serialize;
use serde_json::ser::Formatter; use serde_json::ser::Formatter;

View file

@ -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 arrayvec;
pub mod blob; pub mod blob;

View file

@ -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::error::Error;
use std::fmt::{Debug, Display}; use std::fmt::{Debug, Display};

View file

@ -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)] #[allow(unused_imports)]
use std::mem::{needs_drop, size_of, MaybeUninit}; use std::mem::{needs_drop, size_of, MaybeUninit};

View file

@ -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::ops::{Deref, DerefMut};
use std::ptr::NonNull; use std::ptr::NonNull;

View file

@ -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::collections::VecDeque;
use std::sync::Arc; use std::sync::Arc;

View file

@ -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; use std::mem::MaybeUninit;

View file

@ -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::hash::{Hash, Hasher};
use std::mem::MaybeUninit; 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 /// 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). /// 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> { pub struct RingBufferMap<K: Eq + PartialEq + Hash + Clone, V, const C: usize, const B: usize> {
salt: u32,
entries: [Entry<K, V>; C], entries: [Entry<K, V>; C],
salt: u32,
buckets: [u16; B], buckets: [u16; B],
entry_ptr: u16, entry_ptr: u16,
} }

View file

@ -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}; use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
/// Variant version of lock for RwLock with automatic conversion to a write lock as needed. /// Variant version of lock for RwLock with automatic conversion to a write lock as needed.

View file

@ -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::any::TypeId;
use std::mem::{forget, size_of, MaybeUninit}; use std::mem::{forget, size_of, MaybeUninit};

View file

@ -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}; use std::io::{Read, Write};

View file

@ -5,6 +5,22 @@ license = "MPL-2.0"
name = "zssp" name = "zssp"
version = "0.1.0" 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] [dependencies]
zerotier-utils = { path = "../utils" } zerotier-utils = { path = "../utils" }
zerotier-crypto = { path = "../crypto" } zerotier-crypto = { path = "../crypto" }

View file

@ -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::{ use zerotier_crypto::p384::P384KeyPair;
p384::{P384KeyPair, P384PublicKey},
secret::Secret,
};
use crate::{
sessionid::SessionId,
zssp::{ReceiveContext, Session},
};
/// Trait to implement to integrate the session into an application. /// 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, /// Templating the session on this trait lets the code here be almost entirely transport, OS,
/// and use case independent. /// 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 { 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; type Data;
/// Arbitrary object that dereferences to the session, such as Arc<Session<Self>>. /// Data type for incoming packet buffers.
type SessionRef<'a>: Deref<Target = Session<Self>>;
/// A buffer containing data read from the network that can be cached.
/// ///
/// This can be e.g. a pooled buffer that automatically returns itself to the pool when dropped. /// This can be something like Vec<u8> or Box<[u8]> or it can be something like a pooled reusable
/// It can also just be a Vec<u8> or Box<[u8]> or something like that. /// 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]>; 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. /// 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 /// 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. /// 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]; fn get_local_s_public_blob(&self) -> &[u8];
/// Get SHA384(this host's static public key blob). /// Get a reference to this host's static public key's NIST P-384 secret key pair.
///
/// 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.
/// ///
/// This must return the NIST P-384 public key that is contained within the static public key blob. /// 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; 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)>;
} }

View file

@ -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,
];

View file

@ -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 { pub enum Error {
/// The packet was addressed to an unrecognized local session (should usually be ignored) /// The packet was addressed to an unrecognized local session (should usually be ignored)
UnknownLocalSessionId(SessionId), UnknownLocalSessionId,
/// Packet was not well formed /// Packet was not well formed
InvalidPacket, 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. /// 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, FailedAuthentication,
/// New session was rejected by the application layer.
NewSessionRejected,
/// Rekeying failed and session secret has reached its hard usage count limit /// Rekeying failed and session secret has reached its hard usage count limit
MaxKeyLifetimeExceeded, MaxKeyLifetimeExceeded,
/// Attempt to send using session without established key /// Attempt to send using session without established key
SessionNotEstablished, SessionNotEstablished,
/// Packet ignored by rate limiter.
RateLimited,
/// The other peer specified an unrecognized protocol version /// The other peer specified an unrecognized protocol version
UnknownProtocolVersion, UnknownProtocolVersion,
@ -36,6 +36,9 @@ pub enum Error {
/// Data object is too large to send, even with fragmentation /// Data object is too large to send, even with fragmentation
DataTooLarge, 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. /// 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, /// This can only ever happen if exceptionally large key blobs or metadata are being used,
@ -43,22 +46,29 @@ pub enum Error {
UnexpectedBufferOverrun, 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 { impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { f.write_str(match self {
Self::UnknownLocalSessionId(id) => f.write_str(format!("UnknownLocalSessionId({})", id).as_str()), Self::UnknownLocalSessionId => "UnknownLocalSessionId",
Self::InvalidPacket => f.write_str("InvalidPacket"), Self::InvalidPacket => "InvalidPacket",
Self::InvalidParameter => f.write_str("InvalidParameter"), Self::InvalidParameter => "InvalidParameter",
Self::FailedAuthentication => f.write_str("FailedAuthentication"), Self::FailedAuthentication => "FailedAuthentication",
Self::NewSessionRejected => f.write_str("NewSessionRejected"), Self::MaxKeyLifetimeExceeded => "MaxKeyLifetimeExceeded",
Self::MaxKeyLifetimeExceeded => f.write_str("MaxKeyLifetimeExceeded"), Self::SessionNotEstablished => "SessionNotEstablished",
Self::SessionNotEstablished => f.write_str("SessionNotEstablished"), Self::UnknownProtocolVersion => "UnknownProtocolVersion",
Self::RateLimited => f.write_str("RateLimited"), Self::DataBufferTooSmall => "DataBufferTooSmall",
Self::UnknownProtocolVersion => f.write_str("UnknownProtocolVersion"), Self::DataTooLarge => "DataTooLarge",
Self::DataBufferTooSmall => f.write_str("DataBufferTooSmall"), Self::OutOfSequence => "OutOfSequence",
Self::DataTooLarge => f.write_str("DataTooLarge"), Self::UnexpectedBufferOverrun => "UnexpectedBufferOverrun",
Self::UnexpectedBufferOverrun => f.write_str("UnexpectedBufferOverrun"), })
}
} }
} }

View file

@ -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 applicationlayer;
mod error; mod error;
mod proto;
mod sessionid; mod sessionid;
mod tests; mod tests;
mod zssp; mod zssp;
pub mod constants;
pub use crate::applicationlayer::ApplicationLayer; pub use crate::applicationlayer::ApplicationLayer;
pub use crate::error::Error; 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::sessionid::SessionId;
pub use crate::zssp::{ReceiveContext, ReceiveResult, Role, Session}; pub use crate::zssp::{Context, ReceiveResult, Session};

219
zssp/src/main.rs Normal file
View 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
View 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);
}
}

View file

@ -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::fmt::Display;
use std::num::NonZeroU64; use std::num::NonZeroU64;
use zerotier_crypto::random; use zerotier_crypto::random;
use zerotier_utils::memory::{array_range, as_byte_array}; 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) /// 48-bit session ID (most significant 16 bits of u64 are unused)
#[derive(Copy, Clone, PartialEq, Eq, Hash)] #[derive(Copy, Clone, PartialEq, Eq, Hash)]
#[repr(transparent)] #[repr(transparent)]
pub struct SessionId(NonZeroU64); // stored little endian internally pub struct SessionId(NonZeroU64); // stored little endian internally
const SESSION_ID_SIZE_BYTES: usize = 6;
impl SessionId { impl SessionId {
pub const SIZE: usize = SESSION_ID_SIZE_BYTES;
pub const NONE: u64 = 0;
pub const MAX: u64 = 0xffffffffffff; pub const MAX: u64 = 0xffffffffffff;
/// Create a new session ID, panicing if 'i' is zero or exceeds MAX. /// 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()) 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 { pub fn random() -> Self {
Self(NonZeroU64::new(((random::xorshift64_random() % (Self::MAX - 1)) + 1).to_le()).unwrap()) 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)] #[inline(always)]
pub(crate) fn new_from_u64_le(i: u64) -> Option<SessionId> { pub(crate) fn new_from_u64_le(i: u64) -> Option<SessionId> {
NonZeroU64::new(i & Self::MAX.to_le()).map(|i| Self(i)) 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. /// Get this session ID as a little-endian byte array.
#[inline(always)] #[inline(always)]
pub(crate) fn as_bytes(&self) -> &[u8; SESSION_ID_SIZE] { pub(crate) fn as_bytes(&self) -> &[u8; Self::SIZE] {
array_range::<u8, 8, 0, SESSION_ID_SIZE>(as_byte_array(&self.0)) array_range::<u8, 8, 0, SESSION_ID_SIZE_BYTES>(as_byte_array(&self.0))
} }
} }

View file

@ -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)] #[allow(unused_imports)]
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
@ -214,3 +223,4 @@ mod tests {
} }
} }
} }
*/

File diff suppressed because it is too large Load diff