Merge pull request #1823 from zerotier/zssp-refactor

zssp-refactor
This commit is contained in:
Adam Ierymenko 2022-12-15 17:44:54 -05:00 committed by GitHub
commit 2ab9e5d40b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 1131 additions and 945 deletions

View file

@ -1,6 +1,7 @@
[workspace]
members = [
"crypto",
"zssp",
"network-hypervisor",
"controller",
"service",

View file

@ -3,5 +3,3 @@
------
Most of this library is just glue to provide a simple safe API around things like OpenSSL or OS-specific crypto APIs.
It also contains [ZSSP](ZSSP.md), the V2 ZeroTier Secure Session Protocol.

View file

@ -298,6 +298,9 @@ mod fruit_flavored {
#[cfg(not(target_os = "macos"))]
mod openssl_aes {
use crate::secret::Secret;
use foreign_types::ForeignTypeRef;
use openssl::cipher::CipherRef;
use openssl::cipher_ctx::{CipherCtx, CipherCtxRef};
use openssl::symm::{Cipher, Crypter, Mode};
use std::cell::UnsafeCell;
use std::mem::MaybeUninit;
@ -385,7 +388,7 @@ mod openssl_aes {
unsafe impl Send for Aes {}
unsafe impl Sync for Aes {}
pub struct AesGcm(Secret<32>, usize, Option<Crypter>, bool);
pub struct AesGcm(Secret<32>, usize, CipherCtx, bool);
impl AesGcm {
/// Construct a new AES-GCM cipher.
@ -395,7 +398,7 @@ mod openssl_aes {
match k.len() {
16 | 24 | 32 => {
s.0[..k.len()].copy_from_slice(k);
Self(s, k.len(), None, encrypt)
Self(s, k.len(), CipherCtx::new().unwrap(), encrypt)
}
_ => {
panic!("AES supports 128, 192, or 256 bits keys");
@ -408,58 +411,64 @@ mod openssl_aes {
#[inline]
pub fn reset_init_gcm(&mut self, iv: &[u8]) {
assert_eq!(iv.len(), 12);
let mut c = Crypter::new(
aes_gcm_by_key_size(self.1),
if self.3 {
Mode::Encrypt
} else {
Mode::Decrypt
},
&self.0 .0[..self.1],
Some(iv),
)
.unwrap();
c.pad(false);
//let _ = c.set_tag_len(16);
let _ = self.2.replace(c);
let t = aes_gcm_by_key_size(self.1);
let key = &self.0 .0[..self.1];
{
let f = match self.3 {
true => CipherCtxRef::encrypt_init,
false => CipherCtxRef::decrypt_init,
};
f(
&mut self.2,
Some(unsafe { CipherRef::from_ptr(t.as_ptr() as *mut _) }),
None,
None,
).unwrap();
self.2.set_key_length(key.len()).unwrap();
if let Some(iv_len) = t.iv_len() {
if iv.len() != iv_len {
self.2.set_iv_length(iv.len()).unwrap();
}
}
f(&mut self.2, None, Some(key), Some(iv)).unwrap();
}
self.2.set_padding(false);
}
#[inline(always)]
pub fn aad(&mut self, aad: &[u8]) {
assert!(self.2.as_mut().unwrap().aad_update(aad).is_ok());
self.2.cipher_update(aad, None).unwrap();
}
/// Encrypt or decrypt (same operation with CTR mode)
#[inline(always)]
pub fn crypt(&mut self, input: &[u8], output: &mut [u8]) {
assert!(self.2.as_mut().unwrap().update(input, output).is_ok());
self.2.cipher_update(input, Some(output)).unwrap();
}
/// Encrypt or decrypt in place (same operation with CTR mode)
#[inline(always)]
pub fn crypt_in_place(&mut self, data: &mut [u8]) {
assert!(self
.2
.as_mut()
.unwrap()
.update(unsafe { &*std::slice::from_raw_parts(data.as_ptr(), data.len()) }, data)
.is_ok());
self.2.cipher_update(unsafe { &*std::slice::from_raw_parts(data.as_ptr(), data.len()) }, Some(data)).unwrap();
}
#[inline(always)]
pub fn finish_encrypt(&mut self) -> [u8; 16] {
let mut tag = [0_u8; 16];
let mut c = self.2.take().unwrap();
assert!(c.finalize(&mut tag).is_ok());
assert!(c.get_tag(&mut tag).is_ok());
self.2.cipher_final(&mut tag).unwrap();
self.2.tag(&mut tag).unwrap();
tag
}
#[inline(always)]
pub fn finish_decrypt(&mut self, expected_tag: &[u8]) -> bool {
let mut c = self.2.take().unwrap();
if c.set_tag(expected_tag).is_ok() {
let result = c.finalize(&mut []).is_ok();
if self.2.set_tag(expected_tag).is_ok() {
let result = self.2.cipher_final(&mut []).is_ok();
result
} else {
false

View file

@ -10,6 +10,5 @@ pub mod salsa;
pub mod secret;
pub mod verified;
pub mod x25519;
pub mod zssp;
pub const ZEROES: [u8; 64] = [0_u8; 64];

View file

@ -33,6 +33,28 @@ pub fn write<W: Write>(w: &mut W, v: u64) -> std::io::Result<()> {
w.write_all(&b[0..i])
}
/// Dencode up to 10 bytes as a varint.
///
/// if the supplied byte slice does not contain a valid varint encoding this will return None.
/// if the supplied byte slice is shorter than expected this will return None.
pub fn decode(b: &[u8]) -> Option<(u64, usize)> {
let mut v = 0_u64;
let mut pos = 0;
let mut i = 0_usize;
while i < b.len() && i < VARINT_MAX_SIZE_BYTES {
let b = b[i];
i += 1;
if b <= 0x7f {
v |= (b as u64).wrapping_shl(pos);
pos += 7;
} else {
v |= ((b & 0x7f) as u64).wrapping_shl(pos);
return Some((v, i));
}
}
return None;
}
/// Read a variable length integer, returning the value and the number of bytes written.
pub fn read<R: Read>(r: &mut R) -> std::io::Result<(u64, usize)> {
let mut v = 0_u64;

35
zssp/Cargo.toml Normal file
View file

@ -0,0 +1,35 @@
[package]
authors = ["ZeroTier, Inc. <contact@zerotier.com>", "Adam Ierymenko <adam.ierymenko@zerotier.com>"]
edition = "2021"
license = "MPL-2.0"
name = "zerotier-zssp"
version = "0.1.0"
[dependencies]
zerotier-utils = { path = "../utils" }
zerotier-crypto = { path = "../crypto" }
pqc_kyber = { path = "../third_party/kyber", features = ["kyber1024", "reference"], default-features = false }
#ed25519-dalek = { version = "1.0.1", features = ["std", "u64_backend"], default-features = false }
#foreign-types = "0.3.1"
#lazy_static = "^1"
#poly1305 = { version = "0.8.0", features = [], default-features = false }
#pqc_kyber = { path = "../third_party/kyber", features = ["kyber1024", "reference"], default-features = false }
#pqc_kyber = { version = "^0", features = ["kyber1024", "reference"], default-features = false }
#rand_core = "0.5.1"
#rand_core_062 = { package = "rand_core", version = "0.6.2" }
#subtle = "2.4.1"
#x25519-dalek = { version = "1.2.0", features = ["std", "u64_backend"], default-features = false }
#[target."cfg(windows)".dependencies]
#openssl = { version = "^0", features = ["vendored"], default-features = false }
#winapi = { version = "^0", features = ["handleapi", "ws2ipdef", "ws2tcpip"] }
#[target."cfg(not(windows))".dependencies]
#openssl = { version = "^0", features = [], default-features = false }
#libc = "^0"
#signal-hook = "^0"
#[dev-dependencies]
#criterion = "0.3"
#sha2 = "^0"
#hex-literal = "^0"

19
zssp/changes.txt Normal file
View file

@ -0,0 +1,19 @@
zssp has been moved into it's own crate.
zssp has been cut up into several files, only the new zssp.rs file contains the critical security path.
Standardized the naming conventions for security variables throughout zssp.
Implemented a safer version of write_all for zssp to use. This has 3 benefits: it completely prevents unknown io errors, making error handling easier and self-documenting; it completely prevents src from being truncated in dest, putting in an extra barrier to prevent catastrophic key truncation; and it has slightly less performance overhead than a write_all.
Implemented a safer version of read_exact for zssp to use. This has similar benefits to the previous change.
Refactored most buffer logic to use safe_read_exact and safe_write_all, the resulting code is less verbose and easier to analyze: Because of this refactor the buffer overrun below was caught.
Fixed a buffer overrun panic when decoding alice_ratchet_key_fingerprint
Renamed variables and added extra intermediate values so encoding and decoding are more obviously symmetric.
Added multiple comments.
Removed Box<EphemeralOffer>, EphemeralOffer is now passed out by reference instead of returned up the stack.

1
zssp/rustfmt.toml Symbolic link
View file

@ -0,0 +1 @@
../rustfmt.toml

72
zssp/src/app_layer.rs Normal file
View file

@ -0,0 +1,72 @@
use std::ops::Deref;
use zerotier_crypto::{p384::{P384KeyPair, P384PublicKey}, secret::Secret};
use crate::{zssp::{Session, ReceiveContext}, ints::SessionId};
/// 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.
pub trait ApplicationLayer: Sized {
/// Arbitrary opaque object associated with a session, such as a connection state object.
type SessionUserData;
/// Arbitrary object that dereferences to the session, such as Arc<Session<Self>>.
type SessionRef: 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.
/// It can also just be a Vec<u8> or Box<[u8]> or something like that.
type IncomingPacketBuffer: AsRef<[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.
///
/// 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(&self, local_session_id: SessionId) -> Option<Self::SessionRef>;
/// 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::SessionUserData)>;
}

108
zssp/src/constants.rs Normal file
View file

@ -0,0 +1,108 @@
/// 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 = 1280;
/// 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 NIST recommended maximum and 1/8 the absolute limit where u32 wraps.
/// As such it should leave plenty of margin against nearing key reuse bounds w/AES-GCM.
pub(crate) const REKEY_AFTER_USES: u64 = 536870912;
/// Maximum random jitter to add to rekey-after usage count.
pub(crate) const REKEY_AFTER_USES_MAX_JITTER: u32 = 1048576;
/// 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 = (u32::MAX - 1024) as u64;
/// 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 E1_TYPE_NONE: u8 = 0;
/// Secondary key type: Kyber1024, PQ forward secrecy enabled.
pub(crate) const E1_TYPE_KYBER1024: u8 = 1;
/// Size of packet header
pub(crate) const HEADER_SIZE: usize = 16;
/// 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;
/// Number of session keys to hold at a given time (current, previous, next).
pub(crate) const KEY_HISTORY_SIZE: usize = 3;
// 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_NOP: u8 = 1;
pub(crate) const PACKET_TYPE_INITIAL_KEY_OFFER: u8 = 2; // "alice"
pub(crate) const PACKET_TYPE_KEY_COUNTER_OFFER: u8 = 3; // "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,
];

119
zssp/src/ints.rs Normal file
View file

@ -0,0 +1,119 @@
use std::{sync::atomic::{AtomicU64, Ordering}};
use zerotier_crypto::random;
use zerotier_utils::memory;
/// "Canonical header" for generating 96-bit AES-GCM nonce and for inclusion in HMACs.
///
/// This is basically the actual header but with fragment count and fragment total set to zero.
/// Fragmentation is not considered when authenticating the entire packet. A separate header
/// check code is used to make fragmentation itself more robust, but that's outside the scope
/// of AEAD authentication.
#[derive(Clone, Copy)]
#[repr(C, packed)]
pub(crate) struct CanonicalHeader(pub u64, pub u32);
impl CanonicalHeader {
#[inline(always)]
pub fn make(session_id: SessionId, packet_type: u8, counter: u32) -> Self {
CanonicalHeader(
(u64::from(session_id) | (packet_type as u64).wrapping_shl(48)).to_le(),
counter.to_le(),
)
}
#[inline(always)]
pub fn as_bytes(&self) -> &[u8; 12] {
memory::as_byte_array(self)
}
}
/// 48-bit session ID (most significant 16 bits of u64 are unused)
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(transparent)]
pub struct SessionId(pub(crate) u64);
impl SessionId {
/// The nil session ID used in messages initiating a new session.
///
/// This is all 1's so that ZeroTier can easily tell the difference between ZSSP init packets
/// and ZeroTier V1 packets.
pub const NIL: SessionId = SessionId(0xffffffffffff);
#[inline]
pub fn new_from_u64(i: u64) -> Option<SessionId> {
if i < Self::NIL.0 {
Some(Self(i))
} else {
None
}
}
#[inline]
pub fn new_random() -> Self {
Self(random::next_u64_secure() % Self::NIL.0)
}
}
impl From<SessionId> for u64 {
#[inline(always)]
fn from(sid: SessionId) -> Self {
sid.0
}
}
/// Outgoing packet counter with strictly ordered atomic semantics.
#[repr(transparent)]
pub(crate) struct Counter(AtomicU64);
impl Counter {
#[inline(always)]
pub fn new() -> Self {
// Using a random value has no security implication. Zero would be fine. This just
// helps randomize packet contents a bit.
Self(AtomicU64::new(random::next_u32_secure() as u64))
}
/// Get the value most recently used to send a packet.
#[inline(always)]
pub fn previous(&self) -> CounterValue {
CounterValue(self.0.load(Ordering::SeqCst))
}
/// Get a counter value for the next packet being sent.
#[inline(always)]
pub fn next(&self) -> CounterValue {
CounterValue(self.0.fetch_add(1, Ordering::SeqCst))
}
}
/// A value of the outgoing packet counter.
///
/// The used portion of the packet counter is the least significant 32 bits, but the internal
/// counter state is kept as a 64-bit integer. This makes it easier to correctly handle
/// key expiration after usage limits are reached without complicated logic to handle 32-bit
/// wrapping. Usage limits are below 2^32 so the actual 32-bit counter will not wrap for a
/// given shared secret key.
#[repr(transparent)]
#[derive(Copy, Clone)]
pub(crate) struct CounterValue(pub u64);
impl CounterValue {
#[inline(always)]
pub fn to_u32(&self) -> u32 {
self.0 as u32
}
}
/// Was this side the one who sent the first offer (Alice) or countered (Bob).
/// Note that role is not fixed. Either side can take either role. It's just who
/// initiated first.
pub enum Role {
Alice,
Bob,
}

10
zssp/src/lib.rs Normal file
View file

@ -0,0 +1,10 @@
mod zssp;
mod app_layer;
mod ints;
mod tests;
pub mod constants;
pub use zssp::{Error, ReceiveResult, ReceiveContext, Session};
pub use app_layer::ApplicationLayer;
pub use ints::{SessionId, Role};

226
zssp/src/tests.rs Normal file
View file

@ -0,0 +1,226 @@
#[cfg(test)]
mod tests {
use std::collections::LinkedList;
use std::sync::{Arc, Mutex};
use zerotier_crypto::hash::SHA384;
use zerotier_crypto::p384::{P384KeyPair, P384PublicKey};
use zerotier_crypto::random;
use zerotier_crypto::secret::Secret;
use zerotier_utils::hex;
#[allow(unused_imports)]
use crate::*;
use constants::*;
struct TestHost {
local_s: P384KeyPair,
local_s_hash: [u8; 48],
psk: Secret<64>,
session: Mutex<Option<Arc<Session<Box<TestHost>>>>>,
session_id_counter: Mutex<u64>,
queue: Mutex<LinkedList<Vec<u8>>>,
key_id: Mutex<[u8; 16]>,
this_name: &'static str,
other_name: &'static str,
}
impl TestHost {
fn new(psk: Secret<64>, this_name: &'static str, other_name: &'static str) -> Self {
let local_s = P384KeyPair::generate();
let local_s_hash = SHA384::hash(local_s.public_key_bytes());
Self {
local_s,
local_s_hash,
psk,
session: Mutex::new(None),
session_id_counter: Mutex::new(1),
queue: Mutex::new(LinkedList::new()),
key_id: Mutex::new([0; 16]),
this_name,
other_name,
}
}
}
impl ApplicationLayer for Box<TestHost> {
type SessionUserData = u32;
type SessionRef = Arc<Session<Box<TestHost>>>;
type IncomingPacketBuffer = Vec<u8>;
type RemoteAddress = u32;
const REKEY_RATE_LIMIT_MS: i64 = 0;
fn get_local_s_public_blob(&self) -> &[u8] {
self.local_s.public_key_bytes()
}
fn get_local_s_public_blob_hash(&self) -> &[u8; 48] {
&self.local_s_hash
}
fn get_local_s_keypair(&self) -> &P384KeyPair {
&self.local_s
}
fn extract_s_public_from_raw(static_public: &[u8]) -> Option<P384PublicKey> {
P384PublicKey::from_bytes(static_public)
}
fn lookup_session(&self, local_session_id: SessionId) -> Option<Self::SessionRef> {
self.session.lock().unwrap().as_ref().and_then(|s| {
if s.id == local_session_id {
Some(s.clone())
} else {
None
}
})
}
fn check_new_session(&self, _: &ReceiveContext<Self>, _: &Self::RemoteAddress) -> bool {
true
}
fn accept_new_session(
&self,
_: &ReceiveContext<Self>,
_: &u32,
_: &[u8],
_: &[u8],
) -> Option<(SessionId, Secret<64>, Self::SessionUserData)> {
loop {
let mut new_id = self.session_id_counter.lock().unwrap();
*new_id += 1;
return Some((SessionId::new_from_u64(*new_id).unwrap(), self.psk.clone(), 0));
}
}
}
#[allow(unused_variables)]
#[test]
fn establish_session() {
let mut data_buf = [0_u8; (1280 - 32) * MAX_FRAGMENTS];
let mut mtu_buffer = [0_u8; 1280];
let mut psk: Secret<64> = Secret::default();
random::fill_bytes_secure(&mut psk.0);
let alice_host = Box::new(TestHost::new(psk.clone(), "alice", "bob"));
let bob_host = Box::new(TestHost::new(psk.clone(), "bob", "alice"));
let alice_rc: Box<ReceiveContext<Box<TestHost>>> = Box::new(ReceiveContext::new(&alice_host));
let bob_rc: Box<ReceiveContext<Box<TestHost>>> = Box::new(ReceiveContext::new(&bob_host));
//println!("zssp: size of session (bytes): {}", std::mem::size_of::<Session<Box<TestHost>>>());
let _ = alice_host.session.lock().unwrap().insert(Arc::new(
Session::start_new(
&alice_host,
|data| bob_host.queue.lock().unwrap().push_front(data.to_vec()),
SessionId::new_random(),
bob_host.local_s.public_key_bytes(),
&[],
&psk,
1,
mtu_buffer.len(),
1,
)
.unwrap(),
));
let mut ts = 0;
for test_loop in 0..256 {
for host in [&alice_host, &bob_host] {
let send_to_other = |data: &mut [u8]| {
if std::ptr::eq(host, &alice_host) {
bob_host.queue.lock().unwrap().push_front(data.to_vec());
} else {
alice_host.queue.lock().unwrap().push_front(data.to_vec());
}
};
let rc = if std::ptr::eq(host, &alice_host) {
&alice_rc
} else {
&bob_rc
};
loop {
if let Some(qi) = host.queue.lock().unwrap().pop_back() {
let qi_len = qi.len();
ts += 1;
let r = rc.receive(host, &0, send_to_other, &mut data_buf, qi, mtu_buffer.len(), ts);
if r.is_ok() {
let r = r.unwrap();
match r {
ReceiveResult::Ok => {
//println!("zssp: {} => {} ({}): Ok", host.other_name, host.this_name, qi_len);
}
ReceiveResult::OkData(data) => {
//println!("zssp: {} => {} ({}): OkData length=={}", host.other_name, host.this_name, qi_len, data.len());
assert!(!data.iter().any(|x| *x != 0x12));
}
ReceiveResult::OkNewSession(new_session) => {
println!(
"zssp: {} => {} ({}): OkNewSession ({})",
host.other_name,
host.this_name,
qi_len,
u64::from(new_session.id)
);
let mut hs = host.session.lock().unwrap();
assert!(hs.is_none());
let _ = hs.insert(Arc::new(new_session));
}
ReceiveResult::Ignored => {
println!("zssp: {} => {} ({}): Ignored", host.other_name, host.this_name, qi_len);
}
}
} else {
println!(
"zssp: {} => {} ({}): error: {}",
host.other_name,
host.this_name,
qi_len,
r.err().unwrap().to_string()
);
panic!();
}
} else {
break;
}
}
data_buf.fill(0x12);
if let Some(session) = host.session.lock().unwrap().as_ref().cloned() {
if session.established() {
{
let mut key_id = host.key_id.lock().unwrap();
let security_info = session.status().unwrap();
if !security_info.0.eq(key_id.as_ref()) {
*key_id = security_info.0;
println!(
"zssp: new key at {}: fingerprint {} ratchet {} kyber {}",
host.this_name,
hex::to_string(key_id.as_ref()),
security_info.2,
security_info.3
);
}
}
for _ in 0..4 {
assert!(session
.send(
send_to_other,
&mut mtu_buffer,
&data_buf[..((random::xorshift64_random() as usize) % data_buf.len())]
)
.is_ok());
}
if (test_loop % 8) == 0 && test_loop >= 8 && host.this_name.eq("alice") {
session.service(host, send_to_other, &[], mtu_buffer.len(), test_loop as i64, true);
}
}
}
}
}
}
}

File diff suppressed because it is too large Load diff