diff --git a/src/challenge.rs b/src/challenge.rs index 26a695b..1a4b68b 100644 --- a/src/challenge.rs +++ b/src/challenge.rs @@ -8,7 +8,7 @@ use crate::proto::*; pub struct ChallengeContext { pub enabled: bool, counter: u64, - antireplay_window: [u64; COUNTER_WINDOW_MAX_OOO], + antireplay_window: [u64; CHALLENGE_COUNTER_WINDOW_MAX_OOO], salt: [u8; SALT_SIZE], } diff --git a/src/context.rs b/src/context.rs index 6b80a09..6e7cc98 100644 --- a/src/context.rs +++ b/src/context.rs @@ -83,9 +83,6 @@ impl Context { /// Create a new session and send initial packet(s) to other side. /// - /// This will return SendError::DataTooLarge if the combined size of the metadata and the local - /// static public blob (as retrieved from the application layer) exceed MAX_INIT_PAYLOAD_SIZE. - /// /// * `app` - Application layer instance /// * `send` - Function to be called to send one or more initial packets to the remote being /// contacted @@ -105,6 +102,9 @@ impl Context { identity: Vec, ) -> Result>, OpenError> { mtu = mtu.max(MIN_TRANSPORT_MTU); + if identity.len() > MAX_IDENTITY_BLOB_SIZE { + return Err(OpenError::IdentityTooLarge); + } let ctx = &self.0; // Process zeta layer. diff --git a/src/fragmentation.rs b/src/fragmentation.rs index 2e0db42..a0ceda1 100644 --- a/src/fragmentation.rs +++ b/src/fragmentation.rs @@ -82,7 +82,7 @@ impl DefragBuffer { vrfy: impl FnOnce(&[u8; PACKET_NONCE_SIZE], usize, usize) -> Result<(), ReceiveError>, ) -> Result)>, ReceiveError> { use crate::result::FaultType::*; - if raw_fragment.len() < HEADER_AUTH_END { + if raw_fragment.len() < MIN_PACKET_SIZE { return Err(byzantine_fault!(InvalidPacket, true)); } diff --git a/src/proto.rs b/src/proto.rs index e7fc4ed..abd14d3 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -2,6 +2,8 @@ use crate::crypto::{AES_GCM_TAG_SIZE, KYBER_CIPHERTEXT_SIZE, KYBER_PUBLIC_KEY_SI /* Common constants */ +/// Minimum size of a valid physical ZSSP packet of any type. Anything smaller is discarded. +pub const MIN_PACKET_SIZE: usize = HEADER_SIZE + AES_GCM_TAG_SIZE; /// Minimum physical MTU for ZSSP to function. /// If an MTU is passed to ZSSP that is lower than this, it will be ignored and instead this value /// will be used. @@ -43,6 +45,8 @@ pub(crate) const PACKET_NONCE_START: usize = HEADER_SIZE - PACKET_NONCE_SIZE; pub(crate) const FRAGMENT_NO_IDX: usize = 4; pub(crate) const FRAGMENT_COUNT_IDX: usize = 5; +/// Maximum number of fragments a single packet may be split into. If a packet cannot fit +/// into this number of fragments it will be dropped. pub(crate) const MAX_FRAGMENTS: usize = 48; /* Key exchange constants */ @@ -66,9 +70,10 @@ pub(crate) const HASHLEN: usize = SHA512_HASH_SIZE; /// The size in bytes of both a ratchet key and a ratchet fingerprint. pub const RATCHET_SIZE: usize = 32; -pub(crate) const PROTOCOL_NAME_NOISE_XK: [u8; HASHLEN] = *b"Noise_XKhfs+psk2_P384+Kyber1024_AESGCM_SHA512\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; -pub(crate) const PROTOCOL_NAME_NOISE_KK: [u8; HASHLEN] = - *b"Noise_KKpsk0_P384_AESGCM_SHA512\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; +/// Initial value of 'h'. +pub(crate) const PROTOCOL_NAME_NOISE_XK: &[u8; HASHLEN] = b"Noise_XKhfs+psk2_P384+Kyber1024_AESGCM_SHA512\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; +/// Initial value of 'ck' for rekeying. +pub(crate) const PROTOCOL_NAME_NOISE_KK: &[u8; HASHLEN] = b"Noise_KKpsk0_P384_AESGCM_SHA512\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; pub(crate) const LABEL_OTP_TO_RATCHET: &[u8; 19] = b"ZSSP_OTP_TO_RATCHET"; pub(crate) const LABEL_KBKDF_CHAIN: &[u8; 4] = b"ZSSP"; @@ -77,9 +82,20 @@ pub(crate) const LABEL_HEADER_KEY: &[u8; 4] = b"ASKH"; pub(crate) const LABEL_KEX_KEY: &[u8; 4] = b"ASKK"; pub(crate) const INIT_COUNTER: u64 = 0; -pub(crate) const EXPIRE_AFTER_USES: u64 = 4294967295; +pub(crate) const EXPIRE_AFTER_USES: u64 = 1<<32 - 1; +/// 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. +/// Increasing this value makes a session consume more memory. pub(crate) const COUNTER_WINDOW_MAX_OOO: usize = 64; +/// Maximum number of counter steps that the counter is allowed to skip ahead. +/// This cannot be changed away from 2^24 without changing the header nonce handling code. pub(crate) const COUNTER_WINDOW_MAX_SKIP_AHEAD: u64 = 1 << 24; +/// Similar to `COUNTER_WINDOW_MAX_OOO`, except this governs the receive context challenge +/// counter rather than the session counter. +/// When Bob issues a challenge to Alice to mitigate DDOS, Bob will only accept Alice's +/// response once, and then its attached counter is added to the window. +pub(crate) const CHALLENGE_COUNTER_WINDOW_MAX_OOO: usize = 32; /* Packet constants */ @@ -101,9 +117,18 @@ pub(crate) const HANDSHAKE_HELLO_MAX_SIZE: usize = HANDSHAKE_HELLO_MIN_SIZE + RA 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 HANDSHAKE_COMPLETION_MIN_SIZE: usize = P384_PUBLIC_KEY_SIZE + AES_GCM_TAG_SIZE + 0 + AES_GCM_TAG_SIZE; +pub(crate) const HANDSHAKE_COMPLETION_MAX_SIZE: usize = HANDSHAKE_COMPLETION_MIN_SIZE + MAX_IDENTITY_BLOB_SIZE; pub(crate) const KEY_CONFIRMATION_SIZE: usize = AES_GCM_TAG_SIZE; pub(crate) const ACKNOWLEDGEMENT_SIZE: usize = AES_GCM_TAG_SIZE; pub(crate) const SESSION_REJECTED_SIZE: usize = AES_GCM_TAG_SIZE; pub(crate) const REKEY_SIZE: usize = P384_PUBLIC_KEY_SIZE + KID_SIZE + AES_GCM_TAG_SIZE + AES_GCM_TAG_SIZE; + +/// The application has the ability to attach a data payload to Alice's handshake. +/// It will be the first payload Bob receives from Alice. +/// The application also must attach a static public identity to their handshake. +/// The combined size of both in bytes must be at most this value. +/// +/// If not ZSSP will return `OpenError::DataTooLarge` and refuse to create a session object. +pub const MAX_IDENTITY_BLOB_SIZE: usize = 4096; diff --git a/src/result.rs b/src/result.rs index c9fd0d6..963017f 100644 --- a/src/result.rs +++ b/src/result.rs @@ -10,6 +10,8 @@ pub enum OpenError { /// An invalid parameter was supplied to the function. InvalidPublicKey, + IdentityTooLarge, + RatchetIoError(IoError), } diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index 878769c..702198a 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -71,11 +71,11 @@ impl SymmetricState { } /// Corresponds to Noise `Initialize` on a SymmetricState. - pub fn initialize(h: [u8; HASHLEN]) -> Self { + pub fn initialize(h: &[u8; HASHLEN]) -> Self { Self { k: Zeroizing::default(), - ck: Zeroizing::new(h), - h, + ck: Zeroizing::new(*h), + h: *h, _app: PhantomData, } } diff --git a/src/zeta.rs b/src/zeta.rs index 19b655d..7113133 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -607,7 +607,7 @@ pub(crate) fn received_x3_trans( ) -> Result>, ReceiveError> { use FaultType::*; // -> s, se - if x3.len() < HANDSHAKE_COMPLETION_MIN_SIZE { + if !(HANDSHAKE_COMPLETION_MIN_SIZE..=HANDSHAKE_COMPLETION_MAX_SIZE).contains(&x3.len()) { return Err(byzantine_fault!(InvalidPacket, true)); } if kid != zeta.kid_recv {