added expiration readout

This commit is contained in:
Monica Moniot
2023-11-21 15:21:17 -05:00
parent 31019ec105
commit 04e9571d1c
6 changed files with 213 additions and 158 deletions
+1 -1
View File
@@ -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"
+4 -4
View File
@@ -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)
}
}
}
+4 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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.