mirror of
https://github.com/zerotier/zssp.git
synced 2026-05-22 16:28:40 -07:00
added expiration readout
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.2.0"
|
||||
|
||||
[lib]
|
||||
name = "zssp"
|
||||
|
||||
@@ -203,8 +203,8 @@ fn alice_main(
|
||||
}
|
||||
Err(e) => {
|
||||
println!("[alice] ERROR {:?}", e);
|
||||
if let ReceiveError::ByzantineFault(e, _) = e {
|
||||
assert!(!e.unnatural())
|
||||
if let ReceiveError::ByzantineFault(e) = e {
|
||||
assert!(!e.unnatural)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -302,8 +302,8 @@ fn bob_main(
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!("[bob] ERROR {:?}", e);
|
||||
if let ReceiveError::ByzantineFault(e, _) = e {
|
||||
assert!(!e.unnatural())
|
||||
if let ReceiveError::ByzantineFault(e) = e {
|
||||
assert!(!e.unnatural)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,8 +170,8 @@ fn alice_main(
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!("[alice] ERROR {:?}", e);
|
||||
if let ReceiveError::ByzantineFault(e, _) = e {
|
||||
assert!(!e.unnatural())
|
||||
if let ReceiveError::ByzantineFault(e) = e {
|
||||
assert!(!e.unnatural)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -260,8 +260,8 @@ fn bob_main(
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!("[bob] ERROR {:?}", e);
|
||||
if let ReceiveError::ByzantineFault(e, _) = e {
|
||||
assert!(!e.unnatural())
|
||||
if let ReceiveError::ByzantineFault(e) = e {
|
||||
assert!(!e.unnatural)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+113
-84
@@ -1,5 +1,5 @@
|
||||
use std::error::Error;
|
||||
use std::fmt::Display;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::application::CryptoLayer;
|
||||
@@ -41,6 +41,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 +79,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 +107,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,43 +118,6 @@ 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.
|
||||
///
|
||||
@@ -150,7 +132,7 @@ pub enum ReceiveError<Crypto: CryptoLayer> {
|
||||
/// 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, Option<Arc<Session<Crypto>>>),
|
||||
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.
|
||||
@@ -171,30 +153,31 @@ pub enum ReceiveError<Crypto: CryptoLayer> {
|
||||
|
||||
macro_rules! fault {
|
||||
($name:expr, $unnatural:ident) => {
|
||||
ReceiveError::ByzantineFault(
|
||||
crate::result::ByzantineFault {
|
||||
#[cfg(feature = "debug")]
|
||||
file: file!(),
|
||||
#[cfg(feature = "debug")]
|
||||
line: line!(),
|
||||
error: $name,
|
||||
unnatural: $unnatural,
|
||||
},
|
||||
None,
|
||||
)
|
||||
ReceiveError::ByzantineFault(crate::result::ByzantineFault {
|
||||
#[cfg(feature = "debug")]
|
||||
file: file!(),
|
||||
#[cfg(feature = "debug")]
|
||||
line: line!(),
|
||||
error: $name,
|
||||
unnatural: $unnatural,
|
||||
session: None,
|
||||
caused_expiration: false,
|
||||
})
|
||||
};
|
||||
($name:expr, $unnatural:ident, $session:ident) => {
|
||||
ReceiveError::ByzantineFault(
|
||||
crate::result::ByzantineFault {
|
||||
#[cfg(feature = "debug")]
|
||||
file: file!(),
|
||||
#[cfg(feature = "debug")]
|
||||
line: line!(),
|
||||
error: $name,
|
||||
unnatural: $unnatural,
|
||||
},
|
||||
Some($session.clone()),
|
||||
)
|
||||
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,
|
||||
})
|
||||
};
|
||||
}
|
||||
pub(crate) use fault;
|
||||
@@ -265,8 +248,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),
|
||||
@@ -275,8 +258,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",
|
||||
@@ -288,8 +271,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",
|
||||
@@ -302,25 +300,57 @@ 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<Crypto: CryptoLayer> std::fmt::Debug for ReceiveError<Crypto>
|
||||
impl<Crypto: CryptoLayer> fmt::Debug for ReceiveError<Crypto>
|
||||
where
|
||||
Crypto::SessionData: std::fmt::Debug,
|
||||
Crypto::SessionData: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::ByzantineFault(arg0, arg1) => f.debug_tuple("ByzantineFault").field(arg0).field(arg1).finish(),
|
||||
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(),
|
||||
@@ -328,11 +358,10 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Crypto: CryptoLayer> Display for ReceiveError<Crypto> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
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::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),
|
||||
@@ -340,4 +369,4 @@ impl<Crypto: CryptoLayer> Display for ReceiveError<Crypto> {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<Crypto: CryptoLayer> Error for ReceiveError<Crypto> where Crypto::SessionData: std::fmt::Debug {}
|
||||
impl<Crypto: CryptoLayer> Error for ReceiveError<Crypto> where Crypto::SessionData: fmt::Debug {}
|
||||
|
||||
+60
-52
@@ -594,12 +594,16 @@ pub(crate) fn received_x2_trans<Crypto: CryptoLayer, App: ApplicationLayer<Crypt
|
||||
if c >= COUNTER_WINDOW_MAX_SKIP_AHEAD || &n[AES_GCM_NONCE_SIZE - 3..] != &x2[x2.len() - 3..] {
|
||||
return Err(fault!(FailedAuth, true, session));
|
||||
}
|
||||
|
||||
if !matches!(&state.beta, ZetaAutomata::A1(_)) {
|
||||
return Err(fault!(FailedAuth, true, session));
|
||||
}
|
||||
let mut should_warn_missing_ratchet = false;
|
||||
let mut result = (|| {
|
||||
let a1 = if let ZetaAutomata::A1(a1) = &state.beta {
|
||||
a1
|
||||
} else {
|
||||
return Err(fault!(FailedAuth, true, session));
|
||||
unreachable!();
|
||||
};
|
||||
let mut noise = a1.noise.clone();
|
||||
let mut i = 0;
|
||||
@@ -749,14 +753,15 @@ pub(crate) fn received_x2_trans<Crypto: CryptoLayer, App: ApplicationLayer<Crypt
|
||||
Ok((x3, reduced))
|
||||
})();
|
||||
|
||||
match result {
|
||||
Err(ReceiveError::ByzantineFault { .. }) => {
|
||||
match &mut result {
|
||||
Err(ReceiveError::ByzantineFault(_)) => {
|
||||
let kex_lock = session.state_machine_lock.lock().unwrap();
|
||||
let state = session.state.read().unwrap();
|
||||
let current_time = app.time();
|
||||
timeout_trans(app, ctx, session, kex_lock, state, current_time, send);
|
||||
// We can only reach this point if we are in state A1, and state A1 cannot expire.
|
||||
debug_assert!(timeout_trans(app, ctx, session, kex_lock, state, current_time, send).is_ok());
|
||||
}
|
||||
Ok((ref mut packet, _)) => send(packet, Some(&session.state.read().unwrap().hk_send)),
|
||||
Ok((packet, _)) => send(packet, Some(&session.state.read().unwrap().hk_send)),
|
||||
_ => {}
|
||||
}
|
||||
result.map(|(_, reduced_service_time)| (should_warn_missing_ratchet, reduced_service_time))
|
||||
@@ -1047,7 +1052,7 @@ pub(crate) fn received_c1_trans<Crypto: CryptoLayer, App: ApplicationLayer<Crypt
|
||||
Err(true) => {
|
||||
drop(state);
|
||||
session.expire();
|
||||
Err(fault!(ExpiredCounter, true, session))
|
||||
Err(fault!(ExpiredCounter, true, session, true))
|
||||
}
|
||||
Err(false) => Err(fault!(OutOfSequence, true, session)),
|
||||
}
|
||||
@@ -1141,7 +1146,7 @@ pub(crate) fn received_d_trans<Crypto: CryptoLayer>(
|
||||
Ok(())
|
||||
}
|
||||
/// Corresponds to the timeout timer Transition Algorithm described in Section 4.1 - Definition 3.
|
||||
/// Returns `None` if this session should be expired.
|
||||
/// Returns `Err(())` if this session should be expired.
|
||||
fn timeout_trans<Crypto: CryptoLayer, App: ApplicationLayer<Crypto>>(
|
||||
app: &mut App,
|
||||
ctx: &Arc<ContextInner<Crypto>>,
|
||||
@@ -1150,9 +1155,9 @@ fn timeout_trans<Crypto: CryptoLayer, App: ApplicationLayer<Crypto>>(
|
||||
state: RwLockReadGuard<'_, MutableState<Crypto>>,
|
||||
current_time: i64,
|
||||
send: impl FnOnce(&mut [u8], Option<&Crypto::PrpEnc>),
|
||||
) -> Option<i64> {
|
||||
) -> Result<i64, ()> {
|
||||
match &state.beta {
|
||||
ZetaAutomata::Null => None,
|
||||
ZetaAutomata::Null => Err(()),
|
||||
ZetaAutomata::A1(_) | ZetaAutomata::A3 { .. } => {
|
||||
let identity = match &state.beta {
|
||||
ZetaAutomata::A1(a1) => &a1.identity,
|
||||
@@ -1199,7 +1204,7 @@ fn timeout_trans<Crypto: CryptoLayer, App: ApplicationLayer<Crypto>>(
|
||||
drop(kex_lock);
|
||||
|
||||
send(&mut x1, None);
|
||||
Some(resend_timer)
|
||||
Ok(resend_timer)
|
||||
}
|
||||
ZetaAutomata::S2 => {
|
||||
// Corresponds to Transition Algorithm 6 found in Section 4.3.
|
||||
@@ -1245,33 +1250,33 @@ fn timeout_trans<Crypto: CryptoLayer, App: ApplicationLayer<Crypto>>(
|
||||
let state = session.state.read().unwrap();
|
||||
|
||||
match send_control(session, &state, PACKET_TYPE_REKEY_INIT, k1, send) {
|
||||
Err(true) => None,
|
||||
_ => Some(resend_timer),
|
||||
Err(true) => Err(()),
|
||||
_ => Ok(resend_timer),
|
||||
}
|
||||
}
|
||||
ZetaAutomata::S1 { .. } => {
|
||||
log!(app, TimeoutKeyConfirm(session));
|
||||
None
|
||||
Err(())
|
||||
}
|
||||
ZetaAutomata::R1 { .. } => {
|
||||
log!(app, TimeoutK1(session));
|
||||
None
|
||||
Err(())
|
||||
}
|
||||
ZetaAutomata::R2 { .. } => {
|
||||
log!(app, TimeoutK2(session));
|
||||
None
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Corresponds to the timer rules of the Zeta State Machine found in Section 4.1 - Definition 3.
|
||||
/// Returns `None` if this session should be expired.
|
||||
/// Returns `Err(())` if this session should be expired.
|
||||
pub(crate) fn process_timers<Crypto: CryptoLayer, App: ApplicationLayer<Crypto>>(
|
||||
app: &mut App,
|
||||
ctx: &Arc<ContextInner<Crypto>>,
|
||||
session: &Arc<Session<Crypto>>,
|
||||
current_time: i64,
|
||||
send: impl FnOnce(&mut [u8], Option<&Crypto::PrpEnc>),
|
||||
) -> Option<i64> {
|
||||
) -> Result<i64, ()> {
|
||||
let kex_lock = session.state_machine_lock.lock().unwrap();
|
||||
let state = session.state.read().unwrap();
|
||||
if state.timeout_timer <= current_time {
|
||||
@@ -1284,16 +1289,16 @@ pub(crate) fn process_timers<Crypto: CryptoLayer, App: ApplicationLayer<Crypto>>
|
||||
// Corresponds to the resend timer rules found in Section 4.1 - Definition 3.
|
||||
|
||||
let (packet_type, control_payload) = match &state.beta {
|
||||
ZetaAutomata::Null => return None,
|
||||
ZetaAutomata::Null => return Err(()),
|
||||
ZetaAutomata::A1(a1) => {
|
||||
log!(app, ResentX1(session));
|
||||
send(&mut a1.x1.clone(), None);
|
||||
return Some(resend_next);
|
||||
return Ok(resend_next);
|
||||
}
|
||||
ZetaAutomata::A3(a3) => {
|
||||
log!(app, ResentX3(session));
|
||||
send(&mut a3.x3.clone(), Some(&state.hk_send));
|
||||
return Some(resend_next);
|
||||
return Ok(resend_next);
|
||||
}
|
||||
ZetaAutomata::S1 => {
|
||||
log!(app, ResentKeyConfirm(session));
|
||||
@@ -1301,7 +1306,7 @@ pub(crate) fn process_timers<Crypto: CryptoLayer, App: ApplicationLayer<Crypto>>
|
||||
c1.extend([0u8; HEADER_SIZE]);
|
||||
(PACKET_TYPE_KEY_CONFIRM, c1)
|
||||
}
|
||||
ZetaAutomata::S2 => return Some(state.timeout_timer),
|
||||
ZetaAutomata::S2 => return Ok(state.timeout_timer),
|
||||
ZetaAutomata::R1 { k1, .. } => {
|
||||
log!(app, ResentK1(session));
|
||||
(PACKET_TYPE_REKEY_INIT, k1.clone())
|
||||
@@ -1313,11 +1318,11 @@ pub(crate) fn process_timers<Crypto: CryptoLayer, App: ApplicationLayer<Crypto>>
|
||||
};
|
||||
|
||||
match send_control(session, &state, packet_type, control_payload, send) {
|
||||
Err(true) => None,
|
||||
_ => Some(resend_next),
|
||||
Err(true) => Err(()),
|
||||
_ => Ok(resend_next),
|
||||
}
|
||||
} else {
|
||||
Some(ts)
|
||||
Ok(ts)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1382,7 +1387,7 @@ pub(crate) fn received_k1_trans<Crypto: CryptoLayer, App: ApplicationLayer<Crypt
|
||||
// Process message pattern 1 e token.
|
||||
let e_remote = noise
|
||||
.read_e_no_init(hash, hmac, &mut i, &k1)
|
||||
.ok_or_else(|| fault!(FailedAuth, true, session))?;
|
||||
.ok_or_else(|| fault!(FailedAuth, true, session, true))?;
|
||||
// Process message pattern 1 es token.
|
||||
noise.mix_dh_no_init(hmac, &ctx.s_secret, &e_remote);
|
||||
// Process message pattern 1 ss token.
|
||||
@@ -1392,10 +1397,10 @@ pub(crate) fn received_k1_trans<Crypto: CryptoLayer, App: ApplicationLayer<Crypt
|
||||
let k = j + AES_GCM_TAG_SIZE;
|
||||
let tag = k1[j..k].try_into().unwrap();
|
||||
if !noise.decrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_REKEY_INIT, 0), &mut k1[i..j], tag) {
|
||||
return Err(fault!(FailedAuth, true, session));
|
||||
return Err(fault!(FailedAuth, true, session, true));
|
||||
}
|
||||
let kid_send = NonZeroU32::new(u32::from_ne_bytes(k1[i..j].try_into().unwrap()))
|
||||
.ok_or_else(|| fault!(FailedAuth, true, session))?;
|
||||
.ok_or_else(|| fault!(FailedAuth, true, session, true))?;
|
||||
|
||||
let mut k2 = ArrayVec::<u8, HEADERED_REKEY_SIZE>::new();
|
||||
k2.extend([0u8; HEADER_SIZE]);
|
||||
@@ -1461,12 +1466,15 @@ pub(crate) fn received_k1_trans<Crypto: CryptoLayer, App: ApplicationLayer<Crypt
|
||||
match send_control(session, &state, PACKET_TYPE_REKEY_COMPLETE, k2, send) {
|
||||
Ok(()) => Ok(reduced_service_time),
|
||||
Err(false) => Err(fault!(OutOfSequence, true, session)),
|
||||
Err(true) => Err(fault!(ExpiredCounter, true, session)),
|
||||
Err(true) => Err(fault!(ExpiredCounter, true, session, true)),
|
||||
}
|
||||
})();
|
||||
|
||||
if matches!(result, Err(ReceiveError::ByzantineFault { .. })) {
|
||||
session.expire();
|
||||
match &result {
|
||||
Err(ReceiveError::ByzantineFault(fault)) if fault.caused_expiration => {
|
||||
session.expire();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
result
|
||||
}
|
||||
@@ -1493,23 +1501,19 @@ pub(crate) fn received_k2_trans<Crypto: CryptoLayer, App: ApplicationLayer<Crypt
|
||||
// Some rekey packet may have arrived extremely delayed.
|
||||
return Err(fault!(UnknownLocalKeyId, false, session));
|
||||
}
|
||||
if !matches!(&state.beta, ZetaAutomata::R1 { .. }) {
|
||||
// Some rekey packet may have arrived extremely delayed.
|
||||
return Err(fault!(OutOfSequence, false, session));
|
||||
}
|
||||
|
||||
let i = k2.len() - AES_GCM_TAG_SIZE;
|
||||
let tag = k2[i..].try_into().unwrap();
|
||||
let kek_recv = state.key_ref(false).recv.kek.as_ref().unwrap();
|
||||
if !Crypto::Aead::decrypt_in_place(kek_recv, n, &[], &mut k2[..i], &tag) {
|
||||
return Err(fault!(FailedAuth, true, session));
|
||||
}
|
||||
let (_, c) = from_nonce(n);
|
||||
if !session.window.update(c) {
|
||||
return Err(fault!(ExpiredCounter, true, session));
|
||||
}
|
||||
let result = (move || {
|
||||
if let ZetaAutomata::R1 { noise, e_secret, .. } = &state.beta {
|
||||
let i = k2.len() - AES_GCM_TAG_SIZE;
|
||||
let tag = k2[i..].try_into().unwrap();
|
||||
let kek_recv = state.key_ref(false).recv.kek.as_ref().unwrap();
|
||||
if !Crypto::Aead::decrypt_in_place(kek_recv, n, &[], &mut k2[..i], &tag) {
|
||||
return Err(fault!(FailedAuth, true, session));
|
||||
}
|
||||
let (_, c) = from_nonce(n);
|
||||
if !session.window.update(c) {
|
||||
return Err(fault!(ExpiredCounter, true, session));
|
||||
}
|
||||
|
||||
let mut noise = noise.clone();
|
||||
let mut i = 0;
|
||||
let hash = &mut Crypto::Hash::new();
|
||||
@@ -1517,7 +1521,7 @@ pub(crate) fn received_k2_trans<Crypto: CryptoLayer, App: ApplicationLayer<Crypt
|
||||
// Process message pattern 2 e token.
|
||||
let e_remote = noise
|
||||
.read_e_no_init(hash, hmac, &mut i, &k2)
|
||||
.ok_or_else(|| fault!(FailedAuth, true, session))?;
|
||||
.ok_or_else(|| fault!(FailedAuth, true, session, true))?;
|
||||
// Process message pattern 2 ee token.
|
||||
noise.mix_dh_no_init(hmac, e_secret, &e_remote);
|
||||
// Process message pattern 2 se token.
|
||||
@@ -1527,10 +1531,10 @@ pub(crate) fn received_k2_trans<Crypto: CryptoLayer, App: ApplicationLayer<Crypt
|
||||
let k = j + AES_GCM_TAG_SIZE;
|
||||
let tag = k2[j..k].try_into().unwrap();
|
||||
if !noise.decrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_REKEY_COMPLETE, 0), &mut k2[i..j], tag) {
|
||||
return Err(fault!(FailedAuth, true, session));
|
||||
return Err(fault!(FailedAuth, true, session, true));
|
||||
}
|
||||
let kid_send = NonZeroU32::new(u32::from_ne_bytes(k2[i..j].try_into().unwrap()))
|
||||
.ok_or_else(|| fault!(InvalidPacket, true, session))?;
|
||||
.ok_or_else(|| fault!(InvalidPacket, true, session, true))?;
|
||||
|
||||
let new_ratchet_state = create_ratchet_state(hmac, &noise, state.ratchet_state1.chain_len);
|
||||
app.save_ratchet_state(
|
||||
@@ -1583,15 +1587,19 @@ pub(crate) fn received_k2_trans<Crypto: CryptoLayer, App: ApplicationLayer<Crypt
|
||||
match send_control(&session, &state, PACKET_TYPE_KEY_CONFIRM, c1, send) {
|
||||
Ok(()) => Ok(reduced_service_time),
|
||||
Err(false) => Err(fault!(OutOfSequence, true, session)),
|
||||
Err(true) => Err(fault!(ExpiredCounter, true, session)),
|
||||
Err(true) => Err(fault!(ExpiredCounter, true, session, true)),
|
||||
}
|
||||
} else {
|
||||
unreachable!()
|
||||
// Some rekey packet may have arrived extremely delayed.
|
||||
return Err(fault!(OutOfSequence, false, session));
|
||||
}
|
||||
})();
|
||||
|
||||
if matches!(result, Err(ReceiveError::ByzantineFault { .. })) {
|
||||
session.expire();
|
||||
match &result {
|
||||
Err(ReceiveError::ByzantineFault(fault)) if fault.caused_expiration => {
|
||||
session.expire();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
+31
-13
@@ -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::*;
|
||||
@@ -631,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)
|
||||
@@ -649,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;
|
||||
@@ -682,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
|
||||
@@ -713,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.
|
||||
|
||||
Reference in New Issue
Block a user