mirror of
https://github.com/zerotier/zssp.git
synced 2026-05-22 16:28:40 -07:00
Merge pull request #28 from zerotier/dev
Added Better Visibility into ZSSP Errors and Ratchet Fingerprints
This commit is contained in:
@@ -3,7 +3,7 @@ authors = ["ZeroTier, Inc. <contact@zerotier.com>", "Adam Ierymenko <adam.ieryme
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
name = "zssp"
|
||||
version = "0.1.0"
|
||||
version = "0.3.0"
|
||||
|
||||
[lib]
|
||||
name = "zssp"
|
||||
|
||||
@@ -63,6 +63,7 @@ impl CryptoLayer for TestApplication {
|
||||
type SessionData = u128;
|
||||
|
||||
type IncomingPacketBuffer = Vec<u8>;
|
||||
type FingerprintData = ();
|
||||
}
|
||||
#[allow(unused)]
|
||||
impl ApplicationLayer<TestApplication> for &TestApplication {
|
||||
@@ -82,6 +83,7 @@ impl ApplicationLayer<TestApplication> for &TestApplication {
|
||||
&mut self,
|
||||
remote_static_key: &CrateP384PublicKey,
|
||||
identity: &[u8],
|
||||
_: Option<&()>,
|
||||
) -> AcceptAction<TestApplication> {
|
||||
AcceptAction {
|
||||
session_data: Some(1),
|
||||
@@ -93,15 +95,16 @@ impl ApplicationLayer<TestApplication> for &TestApplication {
|
||||
fn restore_by_fingerprint(
|
||||
&mut self,
|
||||
ratchet_fingerprint: &[u8; RATCHET_SIZE],
|
||||
) -> Result<Option<RatchetState>, std::io::Error> {
|
||||
) -> Result<Option<(RatchetState, ())>, std::io::Error> {
|
||||
let ratchets = self.ratchets.lock().unwrap();
|
||||
Ok(ratchets.rf_map.get(ratchet_fingerprint).cloned())
|
||||
Ok(ratchets.rf_map.get(ratchet_fingerprint).cloned().map(|r| (r, ())))
|
||||
}
|
||||
|
||||
fn restore_by_identity(
|
||||
&mut self,
|
||||
remote_static_key: &CrateP384PublicKey,
|
||||
session_data: &u128,
|
||||
_: Option<&()>,
|
||||
) -> Result<Option<RatchetStates>, std::io::Error> {
|
||||
let ratchets = self.ratchets.lock().unwrap();
|
||||
Ok(ratchets.peer_map.get(session_data).cloned())
|
||||
@@ -187,9 +190,6 @@ fn alice_main(
|
||||
pkt,
|
||||
&mut output_data,
|
||||
) {
|
||||
Ok((Unassociated, _)) => {
|
||||
//println!("[alice] ok");
|
||||
}
|
||||
Ok((Associated(_, event), _)) => match event {
|
||||
Established => {
|
||||
up = true;
|
||||
@@ -201,10 +201,13 @@ fn alice_main(
|
||||
Control => (),
|
||||
_ => panic!(),
|
||||
},
|
||||
Ok(_) => {
|
||||
//println!("[alice] ok");
|
||||
}
|
||||
Err(e) => {
|
||||
println!("[alice] ERROR {:?}", e);
|
||||
if let ReceiveError::ByzantineFault(e) = e {
|
||||
assert!(!e.unnatural())
|
||||
assert!(!e.unnatural)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -278,7 +281,6 @@ fn bob_main(
|
||||
pkt,
|
||||
&mut output_data,
|
||||
) {
|
||||
Ok((Unassociated, _)) => {}
|
||||
Ok((Associated(s, event), _)) => match event {
|
||||
NewSession | NewDowngradedSession => {
|
||||
println!("[bob] new session, took {}s", current_time as f32 / 1000.0);
|
||||
@@ -300,10 +302,11 @@ fn bob_main(
|
||||
Control => (),
|
||||
_ => panic!(),
|
||||
},
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!("[bob] ERROR {:?}", e);
|
||||
if let ReceiveError::ByzantineFault(e) = e {
|
||||
assert!(!e.unnatural())
|
||||
assert!(!e.unnatural)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ impl AsRef<[u8]> for PooledVec {
|
||||
#[allow(unused)]
|
||||
impl DefaultCrypto for TestApplication {
|
||||
type SessionData = ();
|
||||
type LookupData = ();
|
||||
type IncomingPacketBuffer = PooledVec;
|
||||
}
|
||||
|
||||
@@ -76,6 +77,7 @@ impl ApplicationLayer<TestApplication> for &TestApplication {
|
||||
&mut self,
|
||||
remote_static_key: &CrateP384PublicKey,
|
||||
identity: &[u8],
|
||||
_: Option<&()>,
|
||||
) -> AcceptAction<TestApplication> {
|
||||
AcceptAction {
|
||||
session_data: Some(()),
|
||||
@@ -87,7 +89,7 @@ impl ApplicationLayer<TestApplication> for &TestApplication {
|
||||
fn restore_by_fingerprint(
|
||||
&mut self,
|
||||
ratchet_fingerprint: &[u8; RATCHET_SIZE],
|
||||
) -> Result<Option<RatchetState>, std::io::Error> {
|
||||
) -> Result<Option<(RatchetState, ())>, std::io::Error> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
@@ -95,6 +97,7 @@ impl ApplicationLayer<TestApplication> for &TestApplication {
|
||||
&mut self,
|
||||
remote_static_key: &CrateP384PublicKey,
|
||||
session_data: &(),
|
||||
_: Option<&()>,
|
||||
) -> Result<Option<RatchetStates>, std::io::Error> {
|
||||
Ok(None)
|
||||
}
|
||||
@@ -156,9 +159,6 @@ fn alice_main(
|
||||
pkt,
|
||||
&mut output_data,
|
||||
) {
|
||||
Ok((Unassociated, _)) => {
|
||||
//println!("[alice] ok");
|
||||
}
|
||||
Ok((Associated(_, event), _)) => match event {
|
||||
Established => {
|
||||
up = true;
|
||||
@@ -170,10 +170,11 @@ fn alice_main(
|
||||
Control => (),
|
||||
_ => panic!(),
|
||||
},
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!("[alice] ERROR {:?}", e);
|
||||
if let ReceiveError::ByzantineFault(e) = e {
|
||||
assert!(!e.unnatural())
|
||||
assert!(!e.unnatural)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -238,7 +239,6 @@ fn bob_main(
|
||||
pkt,
|
||||
&mut output_data,
|
||||
) {
|
||||
Ok((Unassociated, _)) => {}
|
||||
Ok((Associated(s, event), _)) => match event {
|
||||
NewSession | NewDowngradedSession => {
|
||||
println!("[bob] new session, took {}s", current_time as f32 / 1000.0);
|
||||
@@ -260,10 +260,11 @@ fn bob_main(
|
||||
Control => (),
|
||||
_ => panic!(),
|
||||
},
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!("[bob] ERROR {:?}", e);
|
||||
if let ReceiveError::ByzantineFault(e) = e {
|
||||
assert!(!e.unnatural())
|
||||
assert!(!e.unnatural)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +153,22 @@ pub trait CryptoLayer: Sized {
|
||||
/// each session.
|
||||
type SessionData;
|
||||
|
||||
/// Type for arbitrary opaque object that is attached to a new connection attempt if Alice sends
|
||||
/// us a ratchet fingerprint recognized by `restore_by_fingerprint`.
|
||||
///
|
||||
/// If Alice continues to connect
|
||||
/// with us, then this object will be passed to `check_accept_session` and `restore_by_identity`.
|
||||
/// This is useful if the ratchet fingerprint was derived from a one-time password, in which
|
||||
/// case `FingerprintData` can contain metadata regarding the one-time password. This can be
|
||||
/// used by `check_accept_session` and `restore_by_identity` to perform additional
|
||||
/// authentication checks, such as validating the one-time password as an invitation code.
|
||||
///
|
||||
/// `FingerprintData` can also be used with extreme caution to cache database resources that can
|
||||
/// speed up the expected future calls to `check_accept_session` and `restore_by_identity`.
|
||||
/// If this is done, the implementor is required in `check_accept_session` to verify that the
|
||||
/// cached resources in `FingerprintData` indeed belong to the specified remote peer.
|
||||
type FingerprintData;
|
||||
|
||||
/// Data type for incoming packet buffers.
|
||||
///
|
||||
/// This can be something like `Vec<u8>` or `Box<[u8]>` or it can be something like a pooled
|
||||
@@ -190,6 +206,9 @@ pub trait ApplicationLayer<Crypto: CryptoLayer>: Sized {
|
||||
/// If this function is configured to always return true, it means peers will not be able to
|
||||
/// connect to us unless they had a prior-established ratchet key with us. This is the best way
|
||||
/// for the paranoid to enforce a manual allow-list.
|
||||
///
|
||||
/// Corresponds to the "Hello Requires Recognized Ratchet, π_1" security flag of Transition
|
||||
/// Algorithm 2 within the ZSSP whitepaper.
|
||||
fn hello_requires_recognized_ratchet(&mut self) -> bool;
|
||||
/// This function is called if we, as Alice, attempted to open a session with Bob using a
|
||||
/// non-empty ratchet key, but Bob does not have this ratchet key and wants to downgrade
|
||||
@@ -206,15 +225,29 @@ pub trait ApplicationLayer<Crypto: CryptoLayer>: Sized {
|
||||
/// least one party is misconfigured and got their ratchet keys corrupted or lost, or Bob has
|
||||
/// been compromised and is being impersonated. An attacker must at least have Bob's private
|
||||
/// static key to be able to ask Alice to downgrade.
|
||||
///
|
||||
/// Corresponds to the "Initiator Disallows Downgrade, π_2" security flag of Transition
|
||||
/// Algorithm 3 within the ZSSP whitepaper.
|
||||
fn initiator_disallows_downgrade(&mut self, session: &Arc<Session<Crypto>>) -> bool;
|
||||
/// Function to accept sessions after final negotiation.
|
||||
/// The second argument is the identity that the remote peer sent us. The application
|
||||
/// must verify this identity is associated with the remote peer's static key.
|
||||
///
|
||||
/// The implementor must verify that three arguments, `remote_static_key`, `identity` and
|
||||
/// optionally `fingerprint_data` all belong to the same remote peer, using whatever definition
|
||||
/// of "same remote peer" that the upper protocol chooses.
|
||||
/// `fingerprint_data` is an opaque type that is only `Some` if Alice sent us a ratchet
|
||||
/// fingerprint that was successfully restored by `restore_by_fingerprint`.
|
||||
///
|
||||
/// To prevent desync, if this function specifies that we should connect, no other open session
|
||||
/// with the same remote peer must exist. Drop or call expire on any pre-existing sessions
|
||||
/// before returning.
|
||||
fn check_accept_session(&mut self, remote_static_key: &Crypto::PublicKey, identity: &[u8]) -> AcceptAction<Crypto>;
|
||||
///
|
||||
/// Corresponds to the **Accept** call of Transition Algorithm 4 within the ZSSP whitepaper.
|
||||
fn check_accept_session(
|
||||
&mut self,
|
||||
remote_static_key: &Crypto::PublicKey,
|
||||
identity: &[u8],
|
||||
fingerprint_data: Option<&Crypto::FingerprintData>,
|
||||
) -> AcceptAction<Crypto>;
|
||||
|
||||
/// Lookup a specific ratchet state based on its ratchet fingerprint.
|
||||
/// This function will be called whenever Alice attempts to connect to us with a non-empty
|
||||
@@ -222,10 +255,24 @@ pub trait ApplicationLayer<Crypto: CryptoLayer>: Sized {
|
||||
///
|
||||
/// If a ratchet state with a matching fingerprint could not be found, this function should
|
||||
/// return `Ok(None)`.
|
||||
///
|
||||
/// This function can also return an opaque `FingerprintData` object. If Alice continues to connect
|
||||
/// with us, then this object will be passed to `check_accept_session` and `restore_by_identity`.
|
||||
/// This is useful if the ratchet fingerprint was derived from a one-time password, in which
|
||||
/// case `FingerprintData` can contain metadata regarding the one-time password. This can be
|
||||
/// used by `check_accept_session` and `restore_by_identity` to perform additional
|
||||
/// authentication checks, such as validating the one-time password as an invitation code.
|
||||
///
|
||||
/// `FingerprintData` can also be used with extreme caution to cache database resources that can
|
||||
/// speed up the expected future calls to `check_accept_session` and `restore_by_identity`.
|
||||
/// If this is done, the implementor is required in `check_accept_session` to verify that the
|
||||
/// cached resources in `FingerprintData` indeed belong to the specified remote peer.
|
||||
///
|
||||
/// Corresponds to the **Restore** call of Transition Algorithm 2 within the ZSSP whitepaper.
|
||||
fn restore_by_fingerprint(
|
||||
&mut self,
|
||||
ratchet_fingerprint: &[u8; RATCHET_SIZE],
|
||||
) -> Result<Option<RatchetState>, std::io::Error>;
|
||||
) -> Result<Option<(RatchetState, Crypto::FingerprintData)>, std::io::Error>;
|
||||
/// Lookup the specific ratchet states based on the identity of the peer being communicated with.
|
||||
/// This function will be called whenever Alice attempts to open a session, or Bob attempts
|
||||
/// to verify Alice's identity.
|
||||
@@ -240,10 +287,13 @@ pub trait ApplicationLayer<Crypto: CryptoLayer>: Sized {
|
||||
/// This function is not responsible for deciding whether or not to connect to this remote peer.
|
||||
/// Filtering peers should be done by the caller to `Context::open` as well as by the
|
||||
/// function `ApplicationLayer::check_accept_session`.
|
||||
///
|
||||
/// Corresponds to the **Restore** call of Transition Algorithm 1 and 4 within the ZSSP whitepaper.
|
||||
fn restore_by_identity(
|
||||
&mut self,
|
||||
remote_static_key: &Crypto::PublicKey,
|
||||
session_data: &Crypto::SessionData,
|
||||
fingerprint_data: Option<&Crypto::FingerprintData>,
|
||||
) -> Result<Option<RatchetStates>, std::io::Error>;
|
||||
/// Atomically commit the update specified by `update_data` to storage, or return an error if
|
||||
/// the update could not be made.
|
||||
@@ -306,12 +356,18 @@ pub struct AcceptAction<Crypto: CryptoLayer> {
|
||||
pub session_data: Option<Crypto::SessionData>,
|
||||
/// Whether or not we will accept a connection with the remote peer when they do not have a
|
||||
/// ratchet key that we think they should have.
|
||||
///
|
||||
/// Corresponds to the "Responder Disallows Downgrade, π_3" security flag of Transition
|
||||
/// Algorithm 4 within the ZSSP whitepaper.
|
||||
pub responder_disallows_downgrade: bool,
|
||||
/// Whether or not to send an explicit rejection packet to the remote peer if we do not create
|
||||
/// a session with them.
|
||||
///
|
||||
/// This field will not be used if `session_data` is `Some` and the remote peer passes all other
|
||||
/// authentication checks.
|
||||
///
|
||||
/// Corresponds to the "Responder Silently Rejects, π_4" security flag of Transition
|
||||
/// Algorithm 4 within the ZSSP whitepaper.
|
||||
pub responder_silently_rejects: bool,
|
||||
}
|
||||
|
||||
@@ -323,7 +379,7 @@ pub trait Sender {
|
||||
/// Send the given fragment on this interface and then return whether or not an error occured.
|
||||
///
|
||||
/// If `true` is returned then sending is cancelled and this instance of `Sender` is dropped.
|
||||
fn send_frag<'a>(&'a mut self, frag: &mut [u8]) -> bool;
|
||||
fn send_frag(&mut self, frag: &mut [u8]) -> bool;
|
||||
}
|
||||
|
||||
/// A trait to genericize the process of borrowing the resources necessary to repeatedly
|
||||
@@ -353,7 +409,7 @@ pub trait SendTo<Crypto: CryptoLayer> {
|
||||
}
|
||||
|
||||
impl<F: FnMut(&mut [u8]) -> bool> Sender for F {
|
||||
fn send_frag<'a>(&'a mut self, frag: &mut [u8]) -> bool {
|
||||
fn send_frag(&mut self, frag: &mut [u8]) -> bool {
|
||||
self(frag)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ pub use openssl_sys;
|
||||
#[cfg(feature = "default-crypto")]
|
||||
pub trait DefaultCrypto {
|
||||
type SessionData;
|
||||
type LookupData;
|
||||
type IncomingPacketBuffer: AsMut<[u8]> + AsRef<[u8]>;
|
||||
}
|
||||
#[cfg(feature = "default-crypto")]
|
||||
@@ -54,5 +55,6 @@ impl<C: DefaultCrypto> crate::application::CryptoLayer for C {
|
||||
type Kem = CrateKyber1024PrivateKey;
|
||||
|
||||
type SessionData = C::SessionData;
|
||||
type FingerprintData = C::LookupData;
|
||||
type IncomingPacketBuffer = C::IncomingPacketBuffer;
|
||||
}
|
||||
|
||||
@@ -177,6 +177,7 @@ impl<'a> AesGcmDecContext for OpenSSLAesGcmDec<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A pool of OpenSSL AES-GCM ciphers.
|
||||
pub struct OpenSSLAesGcmPool {
|
||||
enc: [Mutex<OpenSSLCtx>; 8],
|
||||
dec: [Mutex<OpenSSLCtx>; 8],
|
||||
|
||||
@@ -3,6 +3,7 @@ use rand_core::{CryptoRng, RngCore};
|
||||
|
||||
use crate::crypto::*;
|
||||
|
||||
/// An alias for the P384PublicKey type from the p384 crate.
|
||||
pub type CrateP384PublicKey = PublicKey;
|
||||
impl P384PublicKey for CrateP384PublicKey {
|
||||
fn from_bytes(raw_key: &[u8; P384_PUBLIC_KEY_SIZE]) -> Option<Self> {
|
||||
@@ -15,6 +16,7 @@ impl P384PublicKey for CrateP384PublicKey {
|
||||
}
|
||||
}
|
||||
|
||||
/// An alias for the P384KeyPair type from the p384 crate.
|
||||
pub type CrateP384KeyPair = EphemeralSecret;
|
||||
impl<Rng: RngCore + CryptoRng> P384KeyPair<Rng> for CrateP384KeyPair {
|
||||
type PublicKey = PublicKey;
|
||||
|
||||
@@ -2,7 +2,7 @@ use hmac::{Hmac, Mac};
|
||||
use sha2::{Digest, Sha512};
|
||||
|
||||
use crate::crypto::*;
|
||||
|
||||
/// An alias for the Sha512 type from the sha2 crate.
|
||||
pub type CrateSha512 = Sha512;
|
||||
impl Sha512Hash for CrateSha512 {
|
||||
fn new() -> Self {
|
||||
@@ -20,6 +20,7 @@ impl Sha512Hash for CrateSha512 {
|
||||
}
|
||||
}
|
||||
|
||||
/// A type that implements HMAC SHA512 using the hmac and sha2 crates.
|
||||
pub struct CrateHmacSha512;
|
||||
impl Sha512Hmac for CrateHmacSha512 {
|
||||
fn new() -> Self {
|
||||
|
||||
@@ -93,7 +93,7 @@ impl<Crypto: CryptoLayer> UnassociatedFragCache<Crypto> {
|
||||
} else if self.map[idx1].key == key {
|
||||
idx1
|
||||
} else if self.map[idx0].key == 0 || self.map[idx1].key == 0 {
|
||||
if (fragment_count as usize) > self.frags_unused_size {
|
||||
if fragment_count > self.frags_unused_size {
|
||||
// There are not enough free fragment slots so attempt to expire a bunch of entries.
|
||||
let _ = self.check_for_expiry_inner(Crypto::SETTINGS.resend_time as i64, current_time);
|
||||
}
|
||||
@@ -117,7 +117,7 @@ impl<Crypto: CryptoLayer> UnassociatedFragCache<Crypto> {
|
||||
let mut new_expiry = None;
|
||||
if self.map[idx].key == 0 {
|
||||
// This is a new entry so initialize it.
|
||||
if (fragment_count as usize) <= self.frags_unused_size {
|
||||
if fragment_count <= self.frags_unused_size {
|
||||
new_expiry = Some(current_time + Crypto::SETTINGS.fragment_assembly_timeout as i64);
|
||||
let entry = &mut self.map[idx];
|
||||
entry.key = key;
|
||||
@@ -148,7 +148,7 @@ impl<Crypto: CryptoLayer> UnassociatedFragCache<Crypto> {
|
||||
entry.packet_size = new_size;
|
||||
entry.fragment_have |= got;
|
||||
|
||||
let frag_idx = (entry.frags_idx as usize + fragment_no as usize) % self.frags.len();
|
||||
let frag_idx = (entry.frags_idx as usize + fragment_no) % self.frags.len();
|
||||
self.frags[frag_idx].write(fragment);
|
||||
|
||||
if entry.fragment_have == 1u64.wrapping_shl(fragment_count as u32) - 1 {
|
||||
@@ -183,7 +183,7 @@ impl<Crypto: CryptoLayer> UnassociatedFragCache<Crypto> {
|
||||
return expiry;
|
||||
}
|
||||
}
|
||||
return i64::MAX;
|
||||
i64::MAX
|
||||
}
|
||||
|
||||
fn invalidate<const DROP: bool>(&mut self, idx: usize) {
|
||||
@@ -257,6 +257,7 @@ fn test_cache() {
|
||||
type Kem = CrateKyber1024PrivateKey;
|
||||
|
||||
type SessionData = ();
|
||||
type FingerprintData = ();
|
||||
type IncomingPacketBuffer = Vec<u8>;
|
||||
}
|
||||
|
||||
|
||||
@@ -53,8 +53,8 @@ impl<Fragment, const MAX_FRAGMENTS: usize> Fragged<Fragment, MAX_FRAGMENTS> {
|
||||
if got & self.have == 0 && self.count == fragment_count as u32 {
|
||||
self.have |= got;
|
||||
unsafe {
|
||||
self.frags.get_unchecked_mut(fragment_no as usize).write(fragment);
|
||||
if self.have == 1u64.wrapping_shl(self.count as u32) - 1 {
|
||||
self.frags.get_unchecked_mut(fragment_no).write(fragment);
|
||||
if self.have == 1u64.wrapping_shl(self.count) - 1 {
|
||||
self.have = 0;
|
||||
self.count = 0;
|
||||
self.nonce = u64::MAX;
|
||||
|
||||
@@ -61,7 +61,7 @@ impl<Application: CryptoLayer> UnassociatedHandshakeCache<Application> {
|
||||
cache.expiries[idx] = expiry;
|
||||
cache.handshakes[idx] = Some(state);
|
||||
self.has_pending.store(true, Ordering::Release);
|
||||
return Some(expiry);
|
||||
Some(expiry)
|
||||
}
|
||||
pub(crate) fn remove(&self, local_id: NonZeroU32) -> bool {
|
||||
let mut cache = self.cache.write().unwrap();
|
||||
|
||||
@@ -94,8 +94,8 @@ pub(crate) const LABEL_RATCHET_STATE: &[u8; 4] = b"ASKR";
|
||||
pub(crate) const LABEL_HEADER_KEY: &[u8; 4] = b"ASKH";
|
||||
pub(crate) const LABEL_KEX_KEY: &[u8; 4] = b"ASKK";
|
||||
|
||||
pub(crate) const EXPIRE_AFTER_USES: u64 = 1 << 32 - 1;
|
||||
pub(crate) const THREAD_SAFE_COUNTER_HARD_EXPIRE: u64 = u64::MAX - 1 << 16;
|
||||
pub(crate) const EXPIRE_AFTER_USES: u64 = (1 << 32) - 1;
|
||||
pub(crate) const THREAD_SAFE_COUNTER_HARD_EXPIRE: u64 = u64::MAX - (1 << 16);
|
||||
/// Determines the number of counters a session will remember. If a counter arrives over
|
||||
/// this amount out of order relative to other received counters, it is likely to be
|
||||
/// rejected on the basis that the session can't remember if this counter was replayed.
|
||||
@@ -137,7 +137,7 @@ pub(crate) const HANDSHAKE_RESPONSE_SIZE: usize =
|
||||
P384_PUBLIC_KEY_SIZE + KYBER_CIPHERTEXT_SIZE + AES_GCM_TAG_SIZE + KID_SIZE + AES_GCM_TAG_SIZE;
|
||||
pub(crate) const HEADERED_HANDSHAKE_RESPONSE_SIZE: usize = HANDSHAKE_RESPONSE_SIZE + HEADER_SIZE;
|
||||
|
||||
pub(crate) const HANDSHAKE_COMPLETION_MIN_SIZE: usize = P384_PUBLIC_KEY_SIZE + AES_GCM_TAG_SIZE + 0 + AES_GCM_TAG_SIZE;
|
||||
pub(crate) const HANDSHAKE_COMPLETION_MIN_SIZE: usize = P384_PUBLIC_KEY_SIZE + AES_GCM_TAG_SIZE + AES_GCM_TAG_SIZE;
|
||||
pub(crate) const HANDSHAKE_COMPLETION_MAX_SIZE: usize = HANDSHAKE_COMPLETION_MIN_SIZE + IDENTITY_MAX_SIZE;
|
||||
|
||||
pub(crate) const HEADERED_HANDSHAKE_COMPLETION_MAX_SIZE: usize = HANDSHAKE_COMPLETION_MAX_SIZE + HEADER_SIZE;
|
||||
|
||||
+135
-68
@@ -1,5 +1,5 @@
|
||||
use std::error::Error;
|
||||
use std::fmt::Display;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::application::CryptoLayer;
|
||||
@@ -16,7 +16,6 @@ pub enum OpenError {
|
||||
/// The session could not be openned as a result.
|
||||
StorageError(std::io::Error),
|
||||
}
|
||||
|
||||
/// An error that can occur when attempting to send data over a session.
|
||||
/// Depending on the error type trying again may not work.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
||||
@@ -41,6 +40,14 @@ pub enum SendError {
|
||||
DataTooLarge,
|
||||
}
|
||||
|
||||
/// The contained session has just expired.
|
||||
///
|
||||
/// An expired session is no longer "owned" by the ZSSP context.
|
||||
/// Therefore it is no longer capable of sending, receiving or being serviced,
|
||||
/// so it should be dropped.
|
||||
#[derive(Clone)]
|
||||
pub struct ExpiredError<Crypto: CryptoLayer>(pub Arc<Session<Crypto>>);
|
||||
|
||||
/// A type of fault occurred because we received a bad packet.
|
||||
///
|
||||
/// An unauthenticated attacker can intentionally trigger any of these, so it is best to
|
||||
@@ -71,11 +78,22 @@ pub enum FaultType {
|
||||
/// Because an unauthenticated remote peer can force these to occur with specific
|
||||
/// contained information, it is recommended in production to either drop these
|
||||
/// immediately, or log them safely to a local output stream and then drop them.
|
||||
#[derive(Debug)]
|
||||
pub struct ByzantineFault {
|
||||
pub struct ByzantineFault<Crypto: CryptoLayer> {
|
||||
/// The session associated with this fault, if there was one.
|
||||
///
|
||||
/// The sender specified this session within their packet, but they were not authenticated,
|
||||
/// so the sender could theoretically be anyone.
|
||||
pub session: Option<Arc<Session<Crypto>>>,
|
||||
/// This field is true if this fault caused the session it was attached to to expire.
|
||||
/// An expired session is no longer "owned" by the ZSSP context.
|
||||
/// Therefore it is no longer capable of sending, receiving or being serviced,
|
||||
/// so it should be dropped.
|
||||
///
|
||||
/// If this returns true then it is guaranteed that the `session` field is occupied.
|
||||
pub caused_expiration: bool,
|
||||
/// The type of fault that has occurred. Be cautious if you choose to read this
|
||||
/// value, as an attacker has control over it.
|
||||
pub(crate) error: FaultType,
|
||||
pub error: FaultType,
|
||||
/// Some byzantine faults within ZSSP are naturally occurring, i.e. they can occur
|
||||
/// between two well behaved and trusted parties executing the protocol.
|
||||
/// This boolean is false if this is one of these faults. If you go to the file and
|
||||
@@ -88,7 +106,7 @@ pub struct ByzantineFault {
|
||||
/// persevered (i.e. bits have been flipped) to be unnatural.
|
||||
/// ZSSP also considers collisions of what are supposed to be uniform random
|
||||
/// numbers to be unnatural.
|
||||
pub(crate) unnatural: bool,
|
||||
pub unnatural: bool,
|
||||
/// The file of this implementation of ZSSP from which this error was generated.
|
||||
#[cfg(feature = "debug")]
|
||||
pub(crate) file: &'static str,
|
||||
@@ -99,47 +117,13 @@ pub struct ByzantineFault {
|
||||
#[cfg(feature = "debug")]
|
||||
pub(crate) line: u32,
|
||||
}
|
||||
// I don't like getter methods but in this case they are the only way to implement
|
||||
// conditionally compiled struct fields without the feature flag causing breaking changes.
|
||||
impl ByzantineFault {
|
||||
/// The type of fault that has occurred. Be cautious if you choose to read this
|
||||
/// value, as an attacker has control over it.
|
||||
pub fn error(&self) -> FaultType {
|
||||
self.error
|
||||
}
|
||||
/// Some byzantine faults within ZSSP are naturally occurring, i.e. they can occur
|
||||
/// between two well behaved and trusted parties executing the protocol.
|
||||
/// This boolean is false if this is one of these faults. If you go to the file and
|
||||
/// line number specified by this error you will find a comment describing
|
||||
/// how and why exactly this fault can occur naturally.
|
||||
///
|
||||
/// Faults that can occur because the underlying communication medium is lossy and
|
||||
/// sequentially inconsistent (as in UDP) are considered naturally occurring.
|
||||
/// However ZSSP considers faults that occur because data integrity has not been
|
||||
/// persevered (i.e. bits have been flipped) to be unnatural.
|
||||
/// ZSSP also considers collisions of what are supposed to be uniform random
|
||||
/// numbers to be unnatural.
|
||||
pub fn unnatural(&self) -> bool {
|
||||
self.unnatural
|
||||
}
|
||||
/// The file of this implementation of ZSSP from which this error was generated.
|
||||
#[cfg(feature = "debug")]
|
||||
pub fn file(&self) -> &'static str {
|
||||
self.file
|
||||
}
|
||||
/// The line number of this implementation of ZSSP from which this error was
|
||||
/// generated. As such this number uniquely identifies each possible fault that
|
||||
/// can occur during ZSSP. Advanced user can use this information to debug more
|
||||
/// complicated usages of ZSSP.
|
||||
#[cfg(feature = "debug")]
|
||||
pub fn line(&self) -> u32 {
|
||||
self.line
|
||||
}
|
||||
}
|
||||
|
||||
/// An error that occurred during the receipt of a given packet.
|
||||
#[derive(Debug)]
|
||||
pub enum ReceiveError {
|
||||
///
|
||||
/// Keep in mind that when one of these occurs it inherently means that the packet from the remote
|
||||
/// peer has either not been authenticated or has failed authentication. As such, an attacker could
|
||||
/// trigger any of these. These errors should only be used for debugging and tracing.
|
||||
pub enum ReceiveError<Crypto: CryptoLayer> {
|
||||
/// A type of fault that can occur because a remote peer sent us a bad packet.
|
||||
/// Such packets will be ignored by ZSSP but a user of ZSSP might want to log
|
||||
/// them for debugging or tracing.
|
||||
@@ -147,11 +131,11 @@ pub enum ReceiveError {
|
||||
/// Because an unauthenticated remote peer can force these to occur with specific
|
||||
/// contained information, it is recommended in production to either drop these
|
||||
/// immediately, or log them safely to a local output stream and then drop them.
|
||||
ByzantineFault(ByzantineFault),
|
||||
ByzantineFault(ByzantineFault<Crypto>),
|
||||
|
||||
/// Rekeying failed and session secret has reached its hard usage count limit.
|
||||
/// The associated session will no longer function and has to be dropped.
|
||||
MaxKeyLifetimeExceeded,
|
||||
MaxKeyLifetimeExceeded(Arc<Session<Crypto>>),
|
||||
|
||||
/// Either the `ApplicationLayer::incoming_session` or `ApplicationLayer::check_accept_session`
|
||||
/// callback rejected the remote peer's attempt to establish a new session.
|
||||
@@ -163,7 +147,7 @@ pub enum ReceiveError {
|
||||
|
||||
/// An error was returned by the `output_buffer` passed to receive.
|
||||
/// The received packet was dropped.
|
||||
WriteError(std::io::Error),
|
||||
WriteError(std::io::Error, Arc<Session<Crypto>>),
|
||||
}
|
||||
|
||||
macro_rules! fault {
|
||||
@@ -175,6 +159,23 @@ macro_rules! fault {
|
||||
line: line!(),
|
||||
error: $name,
|
||||
unnatural: $unnatural,
|
||||
session: None,
|
||||
caused_expiration: false,
|
||||
})
|
||||
};
|
||||
($name:expr, $unnatural:ident, $session:ident) => {
|
||||
fault!($name, $unnatural, $session, false)
|
||||
};
|
||||
($name:expr, $unnatural:ident, $session:ident, $e:ident) => {
|
||||
ReceiveError::ByzantineFault(crate::result::ByzantineFault {
|
||||
#[cfg(feature = "debug")]
|
||||
file: file!(),
|
||||
#[cfg(feature = "debug")]
|
||||
line: line!(),
|
||||
error: $name,
|
||||
unnatural: $unnatural,
|
||||
session: Some($session.clone()),
|
||||
caused_expiration: $e,
|
||||
})
|
||||
};
|
||||
}
|
||||
@@ -183,12 +184,17 @@ pub(crate) use fault;
|
||||
/// Result generated by the context packet receive function, with possible payloads.
|
||||
#[derive(Clone)]
|
||||
pub enum ReceiveOk<Crypto: CryptoLayer> {
|
||||
/// Packet superficially appeared valid but is not associated with a session yet.
|
||||
/// The received packet superficially appeared valid but is not associated with a session yet.
|
||||
/// This can occur because the packet was only a fragment of a larger packet,
|
||||
/// or if it was a control packet that does not go through full Noise authentication.
|
||||
Unassociated,
|
||||
/// Packet was authentic and belongs to this specific session.
|
||||
/// The received packet was authentic and belongs to this specific session.
|
||||
Associated(Arc<Session<Crypto>>, SessionEvent),
|
||||
/// The received packet was a fragment of a larger packet.
|
||||
///
|
||||
/// ***The authenticity of this fragment cannot be fully known yet.***
|
||||
/// This return value should only be used for debugging and tracing purposes.
|
||||
Fragment(Arc<Session<Crypto>>),
|
||||
}
|
||||
/// Something that can occur to an associated session when a packet is received successfully,
|
||||
/// including receiving a payload of decrypted, authenticated data.
|
||||
@@ -241,8 +247,8 @@ pub enum SessionEvent {
|
||||
DowngradedRatchetKey,
|
||||
}
|
||||
|
||||
impl Display for OpenError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
impl fmt::Display for OpenError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
OpenError::IdentityTooLarge => f.write_str("identity too large"),
|
||||
OpenError::StorageError(e) => e.fmt(f),
|
||||
@@ -251,8 +257,8 @@ impl Display for OpenError {
|
||||
}
|
||||
impl Error for OpenError {}
|
||||
|
||||
impl Display for SendError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
impl fmt::Display for SendError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let str = match self {
|
||||
SendError::MtuTooSmall => "mtu too small",
|
||||
SendError::SessionExpired => "session has expired",
|
||||
@@ -264,8 +270,23 @@ impl Display for SendError {
|
||||
}
|
||||
impl Error for SendError {}
|
||||
|
||||
impl Display for FaultType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
impl<Crypto: CryptoLayer> fmt::Debug for ExpiredError<Crypto>
|
||||
where
|
||||
Session<Crypto>: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_tuple("ExpiredError").field(&self.0).finish()
|
||||
}
|
||||
}
|
||||
impl<Crypto: CryptoLayer> fmt::Display for ExpiredError<Crypto> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "session expired")
|
||||
}
|
||||
}
|
||||
impl<Crypto: CryptoLayer> Error for ExpiredError<Crypto> where Session<Crypto>: fmt::Debug {}
|
||||
|
||||
impl fmt::Display for FaultType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let str = match self {
|
||||
FaultType::UnknownLocalKeyId => "packet contained an unknown key id",
|
||||
FaultType::InvalidPacket => "invalid packet received",
|
||||
@@ -278,27 +299,73 @@ impl Display for FaultType {
|
||||
}
|
||||
impl Error for FaultType {}
|
||||
|
||||
impl Display for ByzantineFault {
|
||||
// I don't like getter methods but in this case they are the only way to implement
|
||||
// conditionally compiled struct fields without the feature flag causing breaking changes.
|
||||
impl<Crypto: CryptoLayer> ByzantineFault<Crypto> {
|
||||
/// The file of this implementation of ZSSP from which this error was generated.
|
||||
#[cfg(feature = "debug")]
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
pub fn file(&self) -> &'static str {
|
||||
self.file
|
||||
}
|
||||
/// The line number of this implementation of ZSSP from which this error was
|
||||
/// generated. As such this number uniquely identifies each possible fault that
|
||||
/// can occur during ZSSP. Advanced user can use this information to debug more
|
||||
/// complicated usages of ZSSP.
|
||||
#[cfg(feature = "debug")]
|
||||
pub fn line(&self) -> u32 {
|
||||
self.line
|
||||
}
|
||||
}
|
||||
impl<Crypto: CryptoLayer> fmt::Debug for ByzantineFault<Crypto>
|
||||
where
|
||||
Session<Crypto>: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("ByzantineFault")
|
||||
.field("session", &self.session)
|
||||
.field("caused_expiration", &self.caused_expiration)
|
||||
.field("error", &self.error)
|
||||
.field("unnatural", &self.unnatural)
|
||||
.field("file", &self.file)
|
||||
.field("line", &self.line)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
impl<Crypto: CryptoLayer> fmt::Display for ByzantineFault<Crypto> {
|
||||
#[cfg(feature = "debug")]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{} ({}:{})", self.error, self.file, self.line)
|
||||
}
|
||||
#[cfg(not(feature = "debug"))]
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.error.fmt(f)
|
||||
}
|
||||
}
|
||||
impl Error for ByzantineFault {}
|
||||
impl<Crypto: CryptoLayer> Error for ByzantineFault<Crypto> where Session<Crypto>: fmt::Debug {}
|
||||
|
||||
impl Display for ReceiveError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
impl<Crypto: CryptoLayer> fmt::Debug for ReceiveError<Crypto>
|
||||
where
|
||||
Crypto::SessionData: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ReceiveError::ByzantineFault(e) => e.fmt(f),
|
||||
ReceiveError::MaxKeyLifetimeExceeded => f.write_str("max key lifetime exceeded"),
|
||||
ReceiveError::Rejected => f.write_str("attempt to establish session rejected"),
|
||||
ReceiveError::StorageError(e) => e.fmt(f),
|
||||
ReceiveError::WriteError(e) => e.fmt(f),
|
||||
Self::ByzantineFault(arg) => f.debug_tuple("ByzantineFault").field(arg).finish(),
|
||||
Self::MaxKeyLifetimeExceeded(arg0) => f.debug_tuple("MaxKeyLifetimeExceeded").field(arg0).finish(),
|
||||
Self::Rejected => f.write_str("Rejected"),
|
||||
Self::StorageError(arg0) => f.debug_tuple("StorageError").field(arg0).finish(),
|
||||
Self::WriteError(arg0, arg1) => f.debug_tuple("WriteError").field(arg0).field(arg1).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Error for ReceiveError {}
|
||||
impl<Crypto: CryptoLayer> fmt::Display for ReceiveError<Crypto> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ReceiveError::ByzantineFault(e) => e.fmt(f),
|
||||
ReceiveError::MaxKeyLifetimeExceeded(_) => f.write_str("max key lifetime exceeded"),
|
||||
ReceiveError::Rejected => f.write_str("attempt to establish session rejected"),
|
||||
ReceiveError::StorageError(e) => e.fmt(f),
|
||||
ReceiveError::WriteError(e, _) => e.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<Crypto: CryptoLayer> Error for ReceiveError<Crypto> where Crypto::SessionData: fmt::Debug {}
|
||||
|
||||
@@ -19,7 +19,7 @@ impl<Crypto: CryptoLayer> Clone for SymmetricState<Crypto> {
|
||||
Self {
|
||||
k: self.k.clone(),
|
||||
ck: self.ck.clone(),
|
||||
h: self.h.clone(),
|
||||
h: self.h,
|
||||
_app: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
+191
-162
File diff suppressed because it is too large
Load Diff
+75
-58
@@ -17,7 +17,7 @@ use crate::fragged::Assembled;
|
||||
use crate::handshake_cache::UnassociatedHandshakeCache;
|
||||
use crate::indexed_heap::IndexedBinaryHeap;
|
||||
use crate::proto::*;
|
||||
use crate::result::{fault, FaultType, OpenError, ReceiveError, ReceiveOk, SendError, SessionEvent};
|
||||
use crate::result::{fault, ExpiredError, FaultType, OpenError, ReceiveError, ReceiveOk, SendError, SessionEvent};
|
||||
use crate::zeta::*;
|
||||
#[cfg(feature = "logging")]
|
||||
use crate::LogEvent::*;
|
||||
@@ -71,7 +71,9 @@ impl<Crypto: CryptoLayer> ContextInner<Crypto> {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_fragment_header(incoming_fragment: &[u8]) -> Result<(usize, usize, [u8; AES_GCM_NONCE_SIZE]), ReceiveError> {
|
||||
fn parse_fragment_header<Crypto: CryptoLayer>(
|
||||
incoming_fragment: &[u8],
|
||||
) -> Result<(usize, usize, [u8; AES_GCM_NONCE_SIZE]), ReceiveError<Crypto>> {
|
||||
let fragment_no = incoming_fragment[FRAGMENT_NO_IDX] as usize;
|
||||
let fragment_count = incoming_fragment[FRAGMENT_COUNT_IDX] as usize;
|
||||
if fragment_no >= fragment_count || fragment_count > MAX_FRAGMENTS {
|
||||
@@ -206,7 +208,7 @@ impl<Crypto: CryptoLayer> Context<Crypto> {
|
||||
remote_address: &impl Hash,
|
||||
mut incoming_fragment_buf: Crypto::IncomingPacketBuffer,
|
||||
output_buffer: impl Write,
|
||||
) -> Result<(ReceiveOk<Crypto>, Option<i64>), ReceiveError> {
|
||||
) -> Result<(ReceiveOk<Crypto>, Option<i64>), ReceiveError<Crypto>> {
|
||||
use crate::result::FaultType::*;
|
||||
let ctx = &self.0;
|
||||
send_unassociated_mtu = send_unassociated_mtu.max(MIN_TRANSPORT_MTU);
|
||||
@@ -234,40 +236,38 @@ impl<Crypto: CryptoLayer> Context<Crypto> {
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
//vrfy
|
||||
if packet_type == PACKET_TYPE_HANDSHAKE_RESPONSE {
|
||||
if !matches!(&state.beta, ZetaAutomata::A1(_)) {
|
||||
// A resent handshake response from Bob may have arrived out of order,
|
||||
// after we already received one.
|
||||
return Err(fault!(OutOfSequence, false));
|
||||
}
|
||||
if incoming_counter >= COUNTER_WINDOW_MAX_SKIP_AHEAD {
|
||||
return Err(fault!(ExpiredCounter, true));
|
||||
}
|
||||
} else if PACKET_TYPE_USES_COUNTER_RANGE.contains(&packet_type) {
|
||||
// For DOS resistant reply-protection we need to check that the given counter is
|
||||
// in the window of valid counters immediately.
|
||||
// But for packets larger than 1 fragment we can't actually record the
|
||||
// counter as received until we've authenticated the packet.
|
||||
// So we check the counter window twice, and only update it the second time
|
||||
// after the packet has been authenticated.
|
||||
if !session.window.check(incoming_counter) {
|
||||
// This can occur naturally if packets arrive way out of order, or
|
||||
// if they are duplicates.
|
||||
// This can also be naturally triggered if Bob has just successfully
|
||||
// received the first session key and is reject all of Alice's resends.
|
||||
// This can also occur if a session was manually expired, but not
|
||||
// dropped, and the remote party is still sending us data.
|
||||
return Err(fault!(ExpiredCounter, false));
|
||||
}
|
||||
} else if packet_type == PACKET_TYPE_HANDSHAKE_COMPLETION {
|
||||
// This can be triggered if Bob successfully received a session key and
|
||||
// needs to reject all of Alice's resends of PACKET_TYPE_NOISE_XK_PATTERN_3.
|
||||
return Err(fault!(InvalidPacket, false));
|
||||
} else {
|
||||
return Err(fault!(InvalidPacket, true));
|
||||
//vrfy
|
||||
if packet_type == PACKET_TYPE_HANDSHAKE_RESPONSE {
|
||||
if !matches!(&state.beta, ZetaAutomata::A1(_)) {
|
||||
// A resent handshake response from Bob may have arrived out of order,
|
||||
// after we already received one.
|
||||
return Err(fault!(OutOfSequence, false, session));
|
||||
}
|
||||
if incoming_counter >= COUNTER_WINDOW_MAX_SKIP_AHEAD {
|
||||
return Err(fault!(ExpiredCounter, true, session));
|
||||
}
|
||||
} else if PACKET_TYPE_USES_COUNTER_RANGE.contains(&packet_type) {
|
||||
// For DOS resistant reply-protection we need to check that the given counter is
|
||||
// in the window of valid counters immediately.
|
||||
// But for packets larger than 1 fragment we can't actually record the
|
||||
// counter as received until we've authenticated the packet.
|
||||
// So we check the counter window twice, and only update it the second time
|
||||
// after the packet has been authenticated.
|
||||
if !session.window.check(incoming_counter) {
|
||||
// This can occur naturally if packets arrive way out of order, or
|
||||
// if they are duplicates.
|
||||
// This can also be naturally triggered if Bob has just successfully
|
||||
// received the first session key and is reject all of Alice's resends.
|
||||
// This can also occur if a session was manually expired, but not
|
||||
// dropped, and the remote party is still sending us data.
|
||||
return Err(fault!(ExpiredCounter, false, session));
|
||||
}
|
||||
} else if packet_type == PACKET_TYPE_HANDSHAKE_COMPLETION {
|
||||
// This can be triggered if Bob successfully received a session key and
|
||||
// needs to reject all of Alice's resends of PACKET_TYPE_NOISE_XK_PATTERN_3.
|
||||
return Err(fault!(InvalidPacket, false, session));
|
||||
} else {
|
||||
return Err(fault!(InvalidPacket, true, session));
|
||||
}
|
||||
|
||||
// Handle defragmentation.
|
||||
@@ -282,7 +282,8 @@ impl<Crypto: CryptoLayer> Context<Crypto> {
|
||||
&mut fragment_buffer,
|
||||
);
|
||||
if fragment_buffer.is_empty() {
|
||||
return Ok((ReceiveOk::Unassociated, None));
|
||||
drop(state);
|
||||
return Ok((ReceiveOk::Fragment(session), None));
|
||||
} else {
|
||||
// We have not yet authenticated the sender so we do not report
|
||||
// receiving a packet from them.
|
||||
@@ -308,12 +309,12 @@ impl<Crypto: CryptoLayer> Context<Crypto> {
|
||||
&mut fragment_buffer,
|
||||
);
|
||||
if fragment_buffer.is_empty() {
|
||||
return Ok((ReceiveOk::Unassociated, None));
|
||||
return Ok((ReceiveOk::Fragment(session), None));
|
||||
} else {
|
||||
for fragment in fragment_buffer.as_ref() {
|
||||
buffer
|
||||
.try_extend_from_slice(&fragment.as_ref()[HEADER_SIZE..])
|
||||
.map_err(|_| fault!(InvalidPacket, true))?;
|
||||
.map_err(|_| fault!(InvalidPacket, true, session))?;
|
||||
}
|
||||
// We have not yet authenticated the sender so we do not report
|
||||
// receiving a packet from them.
|
||||
@@ -407,7 +408,7 @@ impl<Crypto: CryptoLayer> Context<Crypto> {
|
||||
log!(app, DIsAuthClosedSession(&session));
|
||||
(SessionEvent::Rejected, None)
|
||||
}
|
||||
_ => return Err(fault!(InvalidPacket, true)), // This is unreachable.
|
||||
_ => return Err(fault!(InvalidPacket, true, session)), // This is unreachable.
|
||||
}
|
||||
};
|
||||
Ok((ReceiveOk::Associated(session, ret.0), ret.1))
|
||||
@@ -428,11 +429,9 @@ impl<Crypto: CryptoLayer> Context<Crypto> {
|
||||
ReceivedRawFragment(packet_type, incoming_counter, fragment_no, fragment_count)
|
||||
);
|
||||
|
||||
{
|
||||
//vrfy
|
||||
if packet_type != PACKET_TYPE_HANDSHAKE_COMPLETION || incoming_counter != 0 {
|
||||
return Err(fault!(InvalidPacket, true));
|
||||
}
|
||||
//vrfy
|
||||
if packet_type != PACKET_TYPE_HANDSHAKE_COMPLETION || incoming_counter != 0 {
|
||||
return Err(fault!(InvalidPacket, true));
|
||||
}
|
||||
|
||||
let mut buffer = ArrayVec::<u8, HANDSHAKE_COMPLETION_MAX_SIZE>::new();
|
||||
@@ -484,7 +483,7 @@ impl<Crypto: CryptoLayer> Context<Crypto> {
|
||||
// This can occur naturally because either Bob's incoming_sessions cache got
|
||||
// full so Alice's incoming session was dropped, or the session this packet
|
||||
// was for was dropped by the application.
|
||||
return Err(fault!(UnknownLocalKeyId, false));
|
||||
Err(fault!(UnknownLocalKeyId, false))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -632,9 +631,14 @@ impl<Crypto: CryptoLayer> Context<Crypto> {
|
||||
///
|
||||
/// * `app` - Interface to application using ZSSP
|
||||
/// * `send_to` - Function to get a sender and an MTU to send something over an active session
|
||||
pub fn service<App: ApplicationLayer<Crypto>>(&self, mut app: App, send_to: impl SendTo<Crypto>) -> i64 {
|
||||
pub fn service<App: ApplicationLayer<Crypto>>(&self, mut app: App, mut send_to: impl SendTo<Crypto>) -> i64 {
|
||||
let current_time = app.time();
|
||||
let next_service_time = self.service_inner(app, send_to, current_time);
|
||||
let next_service_time = loop {
|
||||
match self.service_inner(&mut app, send_to, current_time) {
|
||||
Ok(ts) => break ts,
|
||||
Err((_, s)) => send_to = s,
|
||||
}
|
||||
};
|
||||
let max_interval = Crypto::SETTINGS
|
||||
.fragment_assembly_timeout
|
||||
.min(Crypto::SETTINGS.rekey_timeout)
|
||||
@@ -650,21 +654,33 @@ impl<Crypto: CryptoLayer> Context<Crypto> {
|
||||
/// an `Option<i64>`. This option can contain an updated, reduced timestamp at which this
|
||||
/// function ought to be called again.
|
||||
///
|
||||
/// If this returns an error then it means that a session has timed-out and has been expired.
|
||||
/// An expired session is no longer "owned" by the ZSSP context.
|
||||
/// Therefore it is no longer capable of sending, receiving or being serviced,
|
||||
/// so it should be dropped.
|
||||
///
|
||||
/// A return type of `Err` effectively means that this function should be called again immediately.
|
||||
/// This function should be called repeatedly in a loop until `Ok` is returned.
|
||||
///
|
||||
/// This function should only be used if the caller has direct access to a scheduler that allows
|
||||
/// them to dynamically modify the interval at which this function is repeatedly called.
|
||||
///
|
||||
/// * `app` - Interface to application using ZSSP
|
||||
/// * `send_to` - Function to get a sender and an MTU to send something over an active session
|
||||
pub fn service_scheduled<App: ApplicationLayer<Crypto>>(&self, mut app: App, send_to: impl SendTo<Crypto>) -> i64 {
|
||||
let current_time = app.time();
|
||||
self.service_inner(app, send_to, current_time)
|
||||
}
|
||||
fn service_inner<App: ApplicationLayer<Crypto>>(
|
||||
pub fn service_scheduled<App: ApplicationLayer<Crypto>>(
|
||||
&self,
|
||||
mut app: App,
|
||||
mut send_to: impl SendTo<Crypto>,
|
||||
send_to: impl SendTo<Crypto>,
|
||||
) -> Result<i64, ExpiredError<Crypto>> {
|
||||
let current_time = app.time();
|
||||
self.service_inner(&mut app, send_to, current_time).map_err(|e| e.0)
|
||||
}
|
||||
fn service_inner<App: ApplicationLayer<Crypto>, F: SendTo<Crypto>>(
|
||||
&self,
|
||||
app: &mut App,
|
||||
mut send_to: F,
|
||||
current_time: i64,
|
||||
) -> i64 {
|
||||
) -> Result<i64, (ExpiredError<Crypto>, F)> {
|
||||
let ctx = &self.0;
|
||||
let mut session_queue = ctx.session_queue.lock().unwrap();
|
||||
let mut queue_service_time = i64::MAX;
|
||||
@@ -683,17 +699,18 @@ impl<Crypto: CryptoLayer> Context<Crypto> {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let result = process_timers(&mut app, ctx, &session, current_time, |packet, hk_send| {
|
||||
let result = process_timers(app, ctx, &session, current_time, |packet, hk_send| {
|
||||
if let Some((sender, mut mtu)) = send_to.init_send(&session) {
|
||||
mtu = mtu.max(MIN_TRANSPORT_MTU);
|
||||
send_with_fragmentation(sender, mtu, packet, hk_send);
|
||||
}
|
||||
});
|
||||
if let Some(next_timer) = result {
|
||||
if let Ok(next_timer) = result {
|
||||
queue_service_time = queue_service_time.min(next_timer);
|
||||
session_queue.change_priority(queue_idx, Reverse(next_timer));
|
||||
} else {
|
||||
session.expire_inner(Some(ctx), Some(&mut session_queue));
|
||||
return Err((ExpiredError(session), send_to));
|
||||
}
|
||||
}
|
||||
// This is the only place where `ctx.next_service_time` can be increased. This only works
|
||||
@@ -714,7 +731,7 @@ impl<Crypto: CryptoLayer> Context<Crypto> {
|
||||
let t2 = defrag_service_time.min(handshake_service_time);
|
||||
let t1 = ctx.next_service_time.fetch_min(t2, Ordering::Relaxed);
|
||||
|
||||
t1.min(t2)
|
||||
Ok(t1.min(t2))
|
||||
}
|
||||
/// Returns the exact timestamp at which either `Context::service` or
|
||||
/// `Context::service_scheduled` should be called again.
|
||||
|
||||
@@ -83,7 +83,6 @@ pub(crate) const LABEL_RATCHET_STATE: &[u8; 4] = b"ASKR";
|
||||
pub(crate) const LABEL_HEADER_KEY: &[u8; 4] = b"ASKH";
|
||||
pub(crate) const LABEL_KEX_KEY: &[u8; 4] = b"ASKK";
|
||||
|
||||
//pub(crate) const EXPIRE_AFTER_USES: u64 = (1 << 32) - 1;
|
||||
pub(crate) const HARD_EXPIRATION: u64 = u64::MAX;
|
||||
/// Determines the number of counters a session will remember. If a counter arrives over
|
||||
/// this amount out of order relative to other received counters, it is likely to be
|
||||
|
||||
Binary file not shown.
+82
-81
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user