Almost ready to test...

This commit is contained in:
Adam Ierymenko 2023-02-27 13:36:35 -05:00
commit 6bc69d1465
8 changed files with 531 additions and 318 deletions

View file

@ -5,6 +5,22 @@ license = "MPL-2.0"
name = "zssp" name = "zssp"
version = "0.1.0" version = "0.1.0"
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
panic = 'abort'
[lib]
name = "zssp"
path = "src/lib.rs"
doc = true
[[bin]]
name = "zssp_test"
path = "src/main.rs"
doc = false
[dependencies] [dependencies]
zerotier-utils = { path = "../utils" } zerotier-utils = { path = "../utils" }
zerotier-crypto = { path = "../crypto" } zerotier-crypto = { path = "../crypto" }

View file

@ -6,32 +6,59 @@
* https://www.zerotier.com/ * https://www.zerotier.com/
*/ */
use zerotier_crypto::p384::{P384KeyPair, P384PublicKey}; use zerotier_crypto::p384::P384KeyPair;
/// Trait to implement to integrate the session into an application. /// Trait to implement to integrate the session into an application.
/// ///
/// Templating the session on this trait lets the code here be almost entirely transport, OS, /// Templating the session on this trait lets the code here be almost entirely transport, OS,
/// and use case independent. /// and use case independent.
///
/// The constants exposed in this trait can be redefined from their defaults to change rekey
/// and negotiation timeout behavior. This is discouraged except for testing purposes when low
/// key lifetime values may be desirable to test rekeying. Also note that each side takes turns
/// initiating rekey, so if both sides don't have the same values you'll get asymmetric timing
/// behavior. This will still work as long as the key usage counter doesn't exceed the
/// EXPIRE_AFTER_USES limit.
pub trait ApplicationLayer: Sized { pub trait ApplicationLayer: Sized {
/// Arbitrary opaque object associated with a session, such as a connection state object. /// Rekey after this many key uses.
///
/// The default is 1/4 the recommended NIST limit for AES-GCM. Unless you are transferring
/// a massive amount of data REKEY_AFTER_TIME_MS is probably going to kick in first.
const REKEY_AFTER_USES: u64 = 536870912;
/// Hard expiration after this many uses.
///
/// Attempting to encrypt more than this many messages with a key will cause a hard error
/// and the internal erasure of ephemeral key material. You'll only ever hit this if something
/// goes wrong and rekeying fails.
const EXPIRE_AFTER_USES: u64 = 2147483648;
/// Start attempting to rekey after a key has been in use for this many milliseconds.
///
/// Default is two hours.
const REKEY_AFTER_TIME_MS: i64 = 1000 * 60 * 60 * 2;
/// Maximum random jitter to add to rekey-after time.
///
/// Default is ten minutes.
const REKEY_AFTER_TIME_MS_MAX_JITTER: u32 = 1000 * 60 * 10;
/// Timeout for Noise_XK session negotiation in milliseconds.
///
/// Default is two seconds, which should be enough for even extremely slow links or links
/// over very long distances.
const SESSION_NEGOTIATION_TIMEOUT_MS: i64 = 2000;
/// Type for arbitrary opaque object for use by the application that is attached to each session.
type Data; type Data;
/// A buffer containing data read from the network that can be cached. /// Data type for incoming packet buffers.
/// ///
/// This can be e.g. a pooled buffer that automatically returns itself to the pool when dropped. /// This can be something like Vec<u8> or Box<[u8]> or it can be something like a pooled reusable
/// It can also just be a Vec<u8> or Box<[u8]> or something like that. /// buffer that automatically returns to its pool when ZSSP is done with it. ZSSP may hold these
/// for a short period of time when assembling fragmented packets on the receive path.
type IncomingPacketBuffer: AsRef<[u8]> + AsMut<[u8]>; type IncomingPacketBuffer: AsRef<[u8]> + AsMut<[u8]>;
/// Rate limit for attempts to rekey existing sessions in milliseconds (default: 2000).
const REKEY_RATE_LIMIT_MS: i64 = 2000;
/// Extract the NIST P-384 ECC public key component from a static public key blob or return None on failure.
///
/// This is called to parse the static public key blob from the other end and extract its NIST P-384 public
/// key. SECURITY NOTE: the information supplied here is from the wire so care must be taken to parse it
/// safely and fail on any error or corruption.
fn extract_s_public_from_static_public_blob(static_public: &[u8]) -> Option<P384PublicKey>;
/// Get a reference to this host's static public key blob. /// Get a reference to this host's static public key blob.
/// ///
/// This must contain a NIST P-384 public key but can contain other information. In ZeroTier this /// This must contain a NIST P-384 public key but can contain other information. In ZeroTier this

View file

@ -1,38 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* (c) ZeroTier, Inc.
* https://www.zerotier.com/
*/
use crate::proto::{AES_GCM_TAG_SIZE, HEADER_SIZE};
/// 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.
pub const MIN_TRANSPORT_MTU: usize = 128;
/// Maximum size of init meta-data objects.
pub const MAX_METADATA_SIZE: usize = 256;
/// Start attempting to rekey after a key has been used to send packets this many times.
/// This is 1/4 the recommended NIST limit for AES-GCM key lifetimes under most conditions.
pub(crate) const REKEY_AFTER_USES: u64 = 536870912;
/// Hard expiration after this many uses.
///
/// Use of the key beyond this point is prohibited. If we reach this number of key uses
/// the key will be destroyed in memory and the session will cease to function. A hard
/// error is also generated.
pub(crate) const EXPIRE_AFTER_USES: u64 = REKEY_AFTER_USES * 2;
/// Start attempting to rekey after a key has been in use for this many milliseconds.
pub(crate) const REKEY_AFTER_TIME_MS: i64 = 1000 * 60 * 60; // 1 hour
/// Maximum random jitter to add to rekey-after time.
pub(crate) const REKEY_AFTER_TIME_MS_MAX_JITTER: u32 = 1000 * 60 * 10; // 10 minutes
/// Timeout for incoming sessions in incomplete state in milliseconds.
pub(crate) const INCOMPLETE_SESSION_TIMEOUT: i64 = 1000;

View file

@ -6,11 +6,9 @@
* https://www.zerotier.com/ * https://www.zerotier.com/
*/ */
use crate::sessionid::SessionId;
pub enum Error { pub enum Error {
/// The packet was addressed to an unrecognized local session (should usually be ignored) /// The packet was addressed to an unrecognized local session (should usually be ignored)
UnknownLocalSessionId(SessionId), UnknownLocalSessionId,
/// Packet was not well formed /// Packet was not well formed
InvalidPacket, InvalidPacket,
@ -29,12 +27,6 @@ pub enum Error {
/// Attempt to send using session without established key /// Attempt to send using session without established key
SessionNotEstablished, SessionNotEstablished,
/// Packet ignored by rate limiter.
RateLimited,
/// Packet counter is too far outside window.
OutOfCounterWindow,
/// The other peer specified an unrecognized protocol version /// The other peer specified an unrecognized protocol version
UnknownProtocolVersion, UnknownProtocolVersion,
@ -44,6 +36,9 @@ pub enum Error {
/// Data object is too large to send, even with fragmentation /// Data object is too large to send, even with fragmentation
DataTooLarge, DataTooLarge,
/// Packet counter was outside window or packet arrived with session in an unexpected state.
OutOfSequence,
/// An unexpected buffer overrun occured while attempting to encode or decode a packet. /// An unexpected buffer overrun occured while attempting to encode or decode a packet.
/// ///
/// This can only ever happen if exceptionally large key blobs or metadata are being used, /// This can only ever happen if exceptionally large key blobs or metadata are being used,
@ -61,20 +56,19 @@ impl From<std::io::Error> for Error {
impl std::fmt::Display for Error { impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { f.write_str(match self {
Self::UnknownLocalSessionId(id) => f.write_str(format!("UnknownLocalSessionId({})", id).as_str()), Self::UnknownLocalSessionId => "UnknownLocalSessionId",
Self::InvalidPacket => f.write_str("InvalidPacket"), Self::InvalidPacket => "InvalidPacket",
Self::InvalidParameter => f.write_str("InvalidParameter"), Self::InvalidParameter => "InvalidParameter",
Self::FailedAuthentication => f.write_str("FailedAuthentication"), Self::FailedAuthentication => "FailedAuthentication",
Self::MaxKeyLifetimeExceeded => f.write_str("MaxKeyLifetimeExceeded"), Self::MaxKeyLifetimeExceeded => "MaxKeyLifetimeExceeded",
Self::SessionNotEstablished => f.write_str("SessionNotEstablished"), Self::SessionNotEstablished => "SessionNotEstablished",
Self::RateLimited => f.write_str("RateLimited"), Self::UnknownProtocolVersion => "UnknownProtocolVersion",
Self::OutOfCounterWindow => f.write_str("OutOfCounterWindow"), Self::DataBufferTooSmall => "DataBufferTooSmall",
Self::UnknownProtocolVersion => f.write_str("UnknownProtocolVersion"), Self::DataTooLarge => "DataTooLarge",
Self::DataBufferTooSmall => f.write_str("DataBufferTooSmall"), Self::OutOfSequence => "OutOfSequence",
Self::DataTooLarge => f.write_str("DataTooLarge"), Self::UnexpectedBufferOverrun => "UnexpectedBufferOverrun",
Self::UnexpectedBufferOverrun => f.write_str("UnexpectedBufferOverrun"), })
}
} }
} }

View file

@ -13,9 +13,8 @@ mod sessionid;
mod tests; mod tests;
mod zssp; mod zssp;
pub mod constants;
pub use crate::applicationlayer::ApplicationLayer; pub use crate::applicationlayer::ApplicationLayer;
pub use crate::error::Error; pub use crate::error::Error;
pub use crate::proto::{MAX_METADATA_SIZE, MIN_PACKET_SIZE, MIN_TRANSPORT_MTU};
pub use crate::sessionid::SessionId; pub use crate::sessionid::SessionId;
pub use crate::zssp::{Context, ReceiveResult, Session}; pub use crate::zssp::{Context, ReceiveResult, Session};

3
zssp/src/main.rs Normal file
View file

@ -0,0 +1,3 @@
use zssp::*;
fn main() {}

View file

@ -13,10 +13,18 @@ use zerotier_crypto::hash::{HMAC_SHA384_SIZE, SHA384_HASH_SIZE};
use zerotier_crypto::p384::P384_PUBLIC_KEY_SIZE; use zerotier_crypto::p384::P384_PUBLIC_KEY_SIZE;
use crate::applicationlayer::ApplicationLayer; use crate::applicationlayer::ApplicationLayer;
use crate::constants::*;
use crate::error::Error; use crate::error::Error;
use crate::sessionid::SessionId; use crate::sessionid::SessionId;
/// 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.
pub const MIN_TRANSPORT_MTU: usize = 128;
/// Maximum size of init meta-data objects.
pub const MAX_METADATA_SIZE: usize = 256;
pub(crate) const SESSION_PROTOCOL_VERSION: u8 = 0x00; pub(crate) const SESSION_PROTOCOL_VERSION: u8 = 0x00;
pub(crate) const COUNTER_WINDOW_MAX_OOO: usize = 16; pub(crate) const COUNTER_WINDOW_MAX_OOO: usize = 16;
@ -30,8 +38,8 @@ pub(crate) const PACKET_TYPE_ALICE_REKEY_INIT: u8 = 4;
pub(crate) const PACKET_TYPE_BOB_REKEY_ACK: u8 = 5; pub(crate) const PACKET_TYPE_BOB_REKEY_ACK: u8 = 5;
pub(crate) const HEADER_SIZE: usize = 16; pub(crate) const HEADER_SIZE: usize = 16;
pub(crate) const HEADER_CHECK_ENCRYPT_START: usize = 6; pub(crate) const HEADER_PROTECT_ENCRYPT_START: usize = 6;
pub(crate) const HEADER_CHECK_ENCRYPT_END: usize = 22; pub(crate) const HEADER_PROTECT_ENCRYPT_END: usize = 22;
pub(crate) const KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION: u8 = b'X'; // intermediate keys used in key exchanges pub(crate) const KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION: u8 = b'X'; // intermediate keys used in key exchanges
pub(crate) const KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION: u8 = b'x'; // intermediate keys used in key exchanges pub(crate) const KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION: u8 = b'x'; // intermediate keys used in key exchanges
@ -46,7 +54,7 @@ pub(crate) const MAX_NOISE_HANDSHAKE_SIZE: usize = MAX_NOISE_HANDSHAKE_FRAGMENTS
pub(crate) const BASE_KEY_SIZE: usize = 64; pub(crate) const BASE_KEY_SIZE: usize = 64;
pub(crate) const AES_KEY_SIZE: usize = 32; pub(crate) const AES_KEY_SIZE: usize = 32;
pub(crate) const AES_HEADER_CHECK_KEY_SIZE: usize = 16; pub(crate) const AES_HEADER_PROTECTION_KEY_SIZE: usize = 16;
pub(crate) const AES_GCM_TAG_SIZE: usize = 16; pub(crate) const AES_GCM_TAG_SIZE: usize = 16;
pub(crate) const AES_GCM_NONCE_SIZE: usize = 12; pub(crate) const AES_GCM_NONCE_SIZE: usize = 12;
pub(crate) const AES_CTR_NONCE_SIZE: usize = 12; pub(crate) const AES_CTR_NONCE_SIZE: usize = 12;
@ -62,14 +70,14 @@ pub(crate) struct AliceNoiseXKInit {
// -- start AES-CTR(es) encrypted section (IV is last 12 bytes of alice_noise_e)) // -- start AES-CTR(es) encrypted section (IV is last 12 bytes of alice_noise_e))
pub alice_session_id: [u8; SessionId::SIZE], pub alice_session_id: [u8; SessionId::SIZE],
pub alice_hk_public: [u8; KYBER_PUBLICKEYBYTES], pub alice_hk_public: [u8; KYBER_PUBLICKEYBYTES],
pub header_check_cipher_key: [u8; AES_HEADER_CHECK_KEY_SIZE], pub header_protection_key: [u8; AES_HEADER_PROTECTION_KEY_SIZE],
// -- end encrypted section // -- end encrypted section
pub hmac_es: [u8; HMAC_SHA384_SIZE], pub hmac_es: [u8; HMAC_SHA384_SIZE],
} }
impl AliceNoiseXKInit { impl AliceNoiseXKInit {
pub const ENC_START: usize = HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE; pub const ENC_START: usize = HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE;
pub const AUTH_START: usize = Self::ENC_START + SessionId::SIZE + KYBER_PUBLICKEYBYTES + AES_HEADER_CHECK_KEY_SIZE; pub const AUTH_START: usize = Self::ENC_START + SessionId::SIZE + KYBER_PUBLICKEYBYTES + AES_HEADER_PROTECTION_KEY_SIZE;
pub const SIZE: usize = Self::AUTH_START + HMAC_SHA384_SIZE; pub const SIZE: usize = Self::AUTH_START + HMAC_SHA384_SIZE;
} }
@ -100,7 +108,7 @@ impl BobNoiseXKAck {
pub(crate) struct AliceNoiseXKAck { pub(crate) struct AliceNoiseXKAck {
pub header: [u8; HEADER_SIZE], pub header: [u8; HEADER_SIZE],
pub session_protocol_version: u8, pub session_protocol_version: u8,
// -- start AES-CTR(es_ee) encrypted section (IV is first 12 bytes of SHA384(hk)) // -- start AES-CTR(es_ee) encrypted section (IV is first 12 bytes of hk)
pub alice_static_blob_length: [u8; 2], pub alice_static_blob_length: [u8; 2],
pub alice_static_blob: [u8; ???], pub alice_static_blob: [u8; ???],
pub alice_metadata_length: [u8; 2], pub alice_metadata_length: [u8; 2],
@ -111,27 +119,43 @@ pub(crate) struct AliceNoiseXKAck {
} }
*/ */
pub(crate) const ALICE_NOISE_XK_ACK_ENC_START: usize = HEADER_SIZE + 1;
pub(crate) const ALICE_NOISE_XK_ACK_AUTH_SIZE: usize = HMAC_SHA384_SIZE + HMAC_SHA384_SIZE;
pub(crate) const ALICE_NOISE_XK_ACK_MIN_SIZE: usize = ALICE_NOISE_XK_ACK_ENC_START + 2 + 2 + ALICE_NOISE_XK_ACK_AUTH_SIZE;
#[allow(unused)] #[allow(unused)]
#[repr(C, packed)] #[repr(C, packed)]
pub(crate) struct AliceRekeyInit { pub(crate) struct AliceRekeyInit {
pub header: [u8; HEADER_SIZE], pub header: [u8; HEADER_SIZE],
// -- start AES-GCM encrypted portion (using current key) // -- start AES-GCM encrypted portion (using current key)
pub alice_noise_e: [u8; P384_PUBLIC_KEY_SIZE], pub alice_e: [u8; P384_PUBLIC_KEY_SIZE],
// -- end AES-GCM encrypted portion // -- end AES-GCM encrypted portion
pub gcm_mac: [u8; AES_GCM_TAG_SIZE], pub gcm_mac: [u8; AES_GCM_TAG_SIZE],
} }
impl AliceRekeyInit {
pub const ENC_START: usize = HEADER_SIZE;
pub const AUTH_START: usize = Self::ENC_START + P384_PUBLIC_KEY_SIZE;
pub const SIZE: usize = Self::AUTH_START + AES_GCM_TAG_SIZE;
}
#[allow(unused)] #[allow(unused)]
#[repr(C, packed)] #[repr(C, packed)]
pub(crate) struct BobRekeyAck { pub(crate) struct BobRekeyAck {
pub header: [u8; HEADER_SIZE], pub header: [u8; HEADER_SIZE],
// -- start AES-GCM encrypted portion (using current key) // -- start AES-GCM encrypted portion (using current key)
pub bob_noise_e: [u8; P384_PUBLIC_KEY_SIZE], pub bob_e: [u8; P384_PUBLIC_KEY_SIZE],
pub ee_fingerprint: [u8; SHA384_HASH_SIZE], pub next_key_fingerprint: [u8; SHA384_HASH_SIZE],
// -- end AES-GCM encrypted portion // -- end AES-GCM encrypted portion
pub gcm_mac: [u8; AES_GCM_TAG_SIZE], pub gcm_mac: [u8; AES_GCM_TAG_SIZE],
} }
impl BobRekeyAck {
pub const ENC_START: usize = HEADER_SIZE;
pub const AUTH_START: usize = Self::ENC_START + P384_PUBLIC_KEY_SIZE + SHA384_HASH_SIZE;
pub const SIZE: usize = Self::AUTH_START + AES_GCM_TAG_SIZE;
}
/// Assemble a series of fragments into a buffer and return the length of the assembled packet in bytes. /// Assemble a series of fragments into a buffer and return the length of the assembled packet in bytes.
pub(crate) fn assemble_fragments_into<A: ApplicationLayer>(fragments: &[A::IncomingPacketBuffer], d: &mut [u8]) -> Result<usize, Error> { pub(crate) fn assemble_fragments_into<A: ApplicationLayer>(fragments: &[A::IncomingPacketBuffer], d: &mut [u8]) -> Result<usize, Error> {
let mut l = 0; let mut l = 0;

View file

@ -16,7 +16,7 @@ use std::sync::{Arc, Mutex, RwLock, Weak};
use zerotier_crypto::aes::{Aes, AesCtr, AesGcm}; use zerotier_crypto::aes::{Aes, AesCtr, AesGcm};
use zerotier_crypto::hash::{hmac_sha512, HMACSHA384, HMAC_SHA384_SIZE, SHA384}; use zerotier_crypto::hash::{hmac_sha512, HMACSHA384, HMAC_SHA384_SIZE, SHA384};
use zerotier_crypto::p384::{P384KeyPair, P384PublicKey, P384_PUBLIC_KEY_SIZE}; use zerotier_crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE, P384_PUBLIC_KEY_SIZE};
use zerotier_crypto::secret::Secret; use zerotier_crypto::secret::Secret;
use zerotier_crypto::{random, secure_eq}; use zerotier_crypto::{random, secure_eq};
@ -28,7 +28,6 @@ use zerotier_utils::ringbuffermap::RingBufferMap;
use pqc_kyber::{KYBER_SECRETKEYBYTES, KYBER_SSBYTES}; use pqc_kyber::{KYBER_SECRETKEYBYTES, KYBER_SSBYTES};
use crate::applicationlayer::ApplicationLayer; use crate::applicationlayer::ApplicationLayer;
use crate::constants::*;
use crate::error::Error; use crate::error::Error;
use crate::proto::*; use crate::proto::*;
use crate::sessionid::SessionId; use crate::sessionid::SessionId;
@ -52,11 +51,8 @@ pub enum ReceiveResult<'b, Application: ApplicationLayer> {
/// Packet was valid and a data payload was decoded and authenticated. /// Packet was valid and a data payload was decoded and authenticated.
OkData(Arc<Session<Application>>, &'b mut [u8]), OkData(Arc<Session<Application>>, &'b mut [u8]),
/// Packet was valid and a new session was created, with static public blob and optional meta-data. /// Packet was valid and a new session was created.
OkNewSession(Arc<Session<Application>>, &'b [u8], Option<&'b [u8]>), OkNewSession(Arc<Session<Application>>),
/// Packet appears valid but was ignored as a duplicate or as meaningless given the current state.
Ignored,
/// Packet appears valid but was rejected by the application layer, e.g. a rejected new session attempt. /// Packet appears valid but was rejected by the application layer, e.g. a rejected new session attempt.
Rejected, Rejected,
@ -75,8 +71,7 @@ pub struct Session<Application: ApplicationLayer> {
psk: Secret<BASE_KEY_SIZE>, psk: Secret<BASE_KEY_SIZE>,
send_counter: AtomicU64, send_counter: AtomicU64,
receive_window: [AtomicU64; COUNTER_WINDOW_MAX_OOO], receive_window: [AtomicU64; COUNTER_WINDOW_MAX_OOO],
header_check_cipher: Aes, header_protection_cipher: Aes,
offer: Mutex<EphemeralOffer>,
state: RwLock<State>, state: RwLock<State>,
defrag: Mutex<RingBufferMap<u64, GatherArray<Application::IncomingPacketBuffer, MAX_FRAGMENTS>, 16, 16>>, defrag: Mutex<RingBufferMap<u64, GatherArray<Application::IncomingPacketBuffer, MAX_FRAGMENTS>, 16, 16>>,
} }
@ -89,41 +84,36 @@ struct SessionMaps<Application: ApplicationLayer> {
incomplete: HashMap<SessionId, Arc<NoiseXKIncoming>>, incomplete: HashMap<SessionId, Arc<NoiseXKIncoming>>,
} }
/// State for an incoming incomplete Noise_XK session that isn't fully negotiated yet.
struct NoiseXKIncoming { struct NoiseXKIncoming {
timestamp: i64, timestamp: i64,
alice_session_id: SessionId, alice_session_id: SessionId,
bob_session_id: SessionId, bob_session_id: SessionId,
noise_es_ee: Secret<BASE_KEY_SIZE>, noise_es_ee: Secret<BASE_KEY_SIZE>,
hk: Secret<KYBER_SSBYTES>, hk: Secret<KYBER_SSBYTES>,
header_check_cipher_key: Secret<AES_HEADER_CHECK_KEY_SIZE>, header_protection_key: Secret<AES_HEADER_PROTECTION_KEY_SIZE>,
bob_noise_e_secret: P384KeyPair, bob_noise_e_secret: P384KeyPair,
} }
/// State that needs to be cached for the most recent outgoing offer. struct NoiseXKOutgoing {
timestamp: i64,
alice_noise_e_secret: P384KeyPair,
noise_es: Secret<P384_ECDH_SHARED_SECRET_SIZE>,
alice_hk_secret: Secret<KYBER_SECRETKEYBYTES>,
}
enum EphemeralOffer { enum EphemeralOffer {
None, None,
NoiseXKInit( NoiseXKInit(Box<NoiseXKOutgoing>),
// boxed to discard memory after use since we only enter this state once at setup
Box<(
// alice_e_secret, metadata, noise_es, alice_hk_public, alice_hk_secret, header check key
P384KeyPair,
Option<ArrayVec<u8, MAX_METADATA_SIZE>>,
Secret<48>,
Secret<KYBER_SECRETKEYBYTES>,
)>,
),
RekeyInit(P384KeyPair), RekeyInit(P384KeyPair),
} }
/// Other mutable state within the session.
struct State { struct State {
remote_session_id: Option<SessionId>, remote_session_id: Option<SessionId>,
keys: [Option<SessionKey>; 2], keys: [Option<SessionKey>; 2],
current_key: usize, current_key: usize,
offer: EphemeralOffer,
} }
/// A session key with lifetime information.
struct SessionKey { struct SessionKey {
ratchet_key: Secret<BASE_KEY_SIZE>, // Key used in derivation of the next session key ratchet_key: Secret<BASE_KEY_SIZE>, // Key used in derivation of the next session key
receive_key: Secret<AES_KEY_SIZE>, // Receive side AES-GCM key receive_key: Secret<AES_KEY_SIZE>, // Receive side AES-GCM key
@ -154,19 +144,28 @@ impl<Application: ApplicationLayer> Context<Application> {
/// Perform periodic background service and cleanup tasks. /// Perform periodic background service and cleanup tasks.
/// ///
/// This returns the number of milliseconds until it should be called again. /// This returns the number of milliseconds until it should be called again.
pub fn service(&self, current_time: i64) -> i64 { pub fn service<SendFunction: FnMut(&Arc<Session<Application>>, &mut [u8])>(&self, mut send: SendFunction, current_time: i64) -> i64 {
let mut dead_active = Vec::new(); let mut dead_active = Vec::new();
let mut dead_pending = Vec::new(); let mut dead_pending = Vec::new();
{ {
let sessions = self.sessions.read().unwrap(); let sessions = self.sessions.read().unwrap();
for (id, s) in sessions.active.iter() { for (id, s) in sessions.active.iter() {
if s.strong_count() == 0 { if let Some(session) = s.upgrade() {
let state = session.state.read().unwrap();
if let Some(key) = state.keys[state.current_key].as_ref() {
if key.role_is_bob
&& (current_time >= key.rekey_at_time || session.send_counter.load(Ordering::Relaxed) >= key.rekey_at_counter)
{
session.send_rekey(|b| send(&session, b));
}
}
} else {
dead_active.push(*id); dead_active.push(*id);
} }
} }
for (id, p) in sessions.incomplete.iter() { for (id, p) in sessions.incomplete.iter() {
if (p.timestamp - current_time) > INCOMPLETE_SESSION_TIMEOUT { if (p.timestamp - current_time) > Application::SESSION_NEGOTIATION_TIMEOUT_MS {
dead_pending.push(*id); dead_pending.push(*id);
} }
} }
@ -182,7 +181,7 @@ impl<Application: ApplicationLayer> Context<Application> {
} }
} }
INCOMPLETE_SESSION_TIMEOUT * 2 Application::SESSION_NEGOTIATION_TIMEOUT_MS * 2
} }
/// Create a new session and send initial packet(s) to other side. /// Create a new session and send initial packet(s) to other side.
@ -191,9 +190,9 @@ impl<Application: ApplicationLayer> Context<Application> {
/// * `send` - User-supplied packet sending function /// * `send` - User-supplied packet sending function
/// * `mtu` - Physical MTU for calls to send() /// * `mtu` - Physical MTU for calls to send()
/// * `local_session_id` - This side's session ID /// * `local_session_id` - This side's session ID
/// * `remote_s_public_blob` - Remote side's public key/identity blob /// * `remote_s_public_p384` - Remote side's static public NIST P-384 key
/// * `metadata` - Optional metadata to be included in initial handshake
/// * `psk` - Pre-shared key (use all zero if none) /// * `psk` - Pre-shared key (use all zero if none)
/// * `metadata` - Optional metadata to be included in initial handshake
/// * `application_data` - Arbitrary opaque data to include with session object /// * `application_data` - Arbitrary opaque data to include with session object
#[allow(unused_variables)] #[allow(unused_variables)]
pub fn open<SendFunction: FnMut(&mut [u8])>( pub fn open<SendFunction: FnMut(&mut [u8])>(
@ -201,10 +200,11 @@ impl<Application: ApplicationLayer> Context<Application> {
app: &Application, app: &Application,
mut send: SendFunction, mut send: SendFunction,
mtu: usize, mtu: usize,
remote_s_public_blob: &[u8], remote_s_public_p384: &P384PublicKey,
metadata: Option<&[u8]>,
psk: Secret<BASE_KEY_SIZE>, psk: Secret<BASE_KEY_SIZE>,
metadata: Option<&[u8]>,
application_data: Application::Data, application_data: Application::Data,
current_time: i64,
) -> Result<Arc<Session<Application>>, Error> { ) -> Result<Arc<Session<Application>>, Error> {
if let Some(md) = metadata.as_ref() { if let Some(md) = metadata.as_ref() {
if md.len() > MAX_METADATA_SIZE { if md.len() > MAX_METADATA_SIZE {
@ -212,12 +212,11 @@ impl<Application: ApplicationLayer> Context<Application> {
} }
} }
if let Some(bob_s_public) = Application::extract_s_public_from_static_public_blob(remote_s_public_blob) {
let alice_noise_e_secret = P384KeyPair::generate(); let alice_noise_e_secret = P384KeyPair::generate();
let alice_noise_e = alice_noise_e_secret.public_key_bytes().clone(); let alice_noise_e = alice_noise_e_secret.public_key_bytes().clone();
let noise_es = alice_noise_e_secret.agree(&bob_s_public).ok_or(Error::InvalidParameter)?; let noise_es = alice_noise_e_secret.agree(&remote_s_public_p384).ok_or(Error::InvalidParameter)?;
let alice_hk_secret = pqc_kyber::keypair(&mut random::SecureRandom::default()); let alice_hk_secret = pqc_kyber::keypair(&mut random::SecureRandom::default());
let header_check_cipher_key: [u8; AES_HEADER_CHECK_KEY_SIZE] = random::get_bytes_secure(); let header_protection_key: [u8; AES_HEADER_PROTECTION_KEY_SIZE] = random::get_bytes_secure();
let (local_session_id, session) = { let (local_session_id, session) = {
let mut sessions = self.sessions.write().unwrap(); let mut sessions = self.sessions.write().unwrap();
@ -236,14 +235,18 @@ impl<Application: ApplicationLayer> Context<Application> {
psk, psk,
send_counter: AtomicU64::new(2), // 1 is the counter value for this INIT message send_counter: AtomicU64::new(2), // 1 is the counter value for this INIT message
receive_window: std::array::from_fn(|_| AtomicU64::new(0)), receive_window: std::array::from_fn(|_| AtomicU64::new(0)),
header_check_cipher: Aes::new(&header_check_cipher_key), header_protection_cipher: Aes::new(&header_protection_key),
offer: Mutex::new(EphemeralOffer::NoiseXKInit(Box::new(( state: RwLock::new(State {
remote_session_id: None,
keys: [None, None],
current_key: 0,
offer: EphemeralOffer::NoiseXKInit(Box::new(NoiseXKOutgoing {
timestamp: current_time,
alice_noise_e_secret, alice_noise_e_secret,
metadata.map(|md| ArrayVec::try_from(md).unwrap()), noise_es: noise_es.clone(),
noise_es.clone(), alice_hk_secret: Secret(alice_hk_secret.secret),
Secret(alice_hk_secret.secret), })),
)))), }),
state: RwLock::new(State { remote_session_id: None, keys: [None, None], current_key: 0 }),
defrag: Mutex::new(RingBufferMap::new(random::xorshift64_random() as u32)), defrag: Mutex::new(RingBufferMap::new(random::xorshift64_random() as u32)),
}); });
@ -258,7 +261,7 @@ impl<Application: ApplicationLayer> Context<Application> {
init.alice_noise_e = alice_noise_e; init.alice_noise_e = alice_noise_e;
init.alice_session_id = *local_session_id.as_bytes(); init.alice_session_id = *local_session_id.as_bytes();
init.alice_hk_public = alice_hk_secret.public; init.alice_hk_public = alice_hk_secret.public;
init.header_check_cipher_key = header_check_cipher_key; init.header_protection_key = header_protection_key;
let mut ctr = AesCtr::new(kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION>(noise_es.as_bytes()).as_bytes()); let mut ctr = AesCtr::new(kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION>(noise_es.as_bytes()).as_bytes());
ctr.reset_set_iv(&alice_noise_e[P384_PUBLIC_KEY_SIZE - AES_CTR_NONCE_SIZE..]); ctr.reset_set_iv(&alice_noise_e[P384_PUBLIC_KEY_SIZE - AES_CTR_NONCE_SIZE..]);
@ -274,9 +277,6 @@ impl<Application: ApplicationLayer> Context<Application> {
send_with_fragmentation(&mut send, &mut init_buffer, mtu, PACKET_TYPE_ALICE_NOISE_XK_INIT, None, 0, 1, None)?; send_with_fragmentation(&mut send, &mut init_buffer, mtu, PACKET_TYPE_ALICE_NOISE_XK_INIT, None, 0, 1, None)?;
return Ok(session); return Ok(session);
} else {
return Err(Error::InvalidParameter);
}
} }
/// Receive, authenticate, decrypt, and process a physical wire packet. /// Receive, authenticate, decrypt, and process a physical wire packet.
@ -285,18 +285,38 @@ impl<Application: ApplicationLayer> Context<Application> {
/// wtth an active session this session is supplied, otherwise this parameter is None. The size /// wtth an active session this session is supplied, otherwise this parameter is None. The size
/// of packets to be sent will not exceed the supplied mtu. /// of packets to be sent will not exceed the supplied mtu.
/// ///
/// The check_allow_incoming_session function is called when an initial Noise_XK init message is
/// received. This is before anything is known about the caller. A return value of true proceeds
/// with negotiation. False drops the packet.
///
/// The check_accept_session function is called at the end of negotiation for an incoming session
/// with the caller's static public blob and meta-data if any. It must return the P-384 static public
/// key extracted from the supplied blob, a PSK (or all zeroes if none), and application data to
/// associate with the new session. A return of None abandons the session.
///
/// Note that if check_accept_session accepts and returns Some() the session could still fail with
/// receive() returning an error. A Some() return from check_accept_sesion doesn't guarantee
/// successful new session init.
///
/// * `app` - Interface to application using ZSSP /// * `app` - Interface to application using ZSSP
/// * `check_allow_incoming_session` - Function to call to check whether an unidentified new session should be accepted /// * `check_allow_incoming_session` - Function to call to check whether an unidentified new session should be accepted
/// * `check_accept_session` - Function to accept sessions after final negotiation, or returns None if rejected
/// * `send` - Function to call to send packets /// * `send` - Function to call to send packets
/// * `data_buf` - Buffer to receive decrypted and authenticated object data (an error is returned if too small) /// * `data_buf` - Buffer to receive decrypted and authenticated object data (an error is returned if too small)
/// * `incoming_packet_buf` - Buffer containing incoming wire packet (receive() takes ownership) /// * `incoming_packet_buf` - Buffer containing incoming wire packet (receive() takes ownership)
/// * `mtu` - Physical wire MTU for sending packets /// * `mtu` - Physical wire MTU for sending packets
/// * `current_time` - Current monotonic time in milliseconds /// * `current_time` - Current monotonic time in milliseconds
#[inline] #[inline]
pub fn receive<'b, SendFunction: FnMut(Option<&Arc<Session<Application>>>, &mut [u8]), CheckAllowIncomingSession: FnMut() -> bool>( pub fn receive<
'b,
SendFunction: FnMut(Option<&Arc<Session<Application>>>, &mut [u8]),
CheckAllowIncomingSession: FnMut() -> bool,
CheckAcceptSession: FnMut(&[u8], Option<&[u8]>) -> Option<(P384PublicKey, Secret<64>, Application::Data)>,
>(
&self, &self,
app: &Application, app: &Application,
mut check_allow_incoming_session: CheckAllowIncomingSession, mut check_allow_incoming_session: CheckAllowIncomingSession,
mut check_accept_session: CheckAcceptSession,
mut send: SendFunction, mut send: SendFunction,
data_buf: &'b mut [u8], data_buf: &'b mut [u8],
mut incoming_packet_buf: Application::IncomingPacketBuffer, mut incoming_packet_buf: Application::IncomingPacketBuffer,
@ -308,12 +328,12 @@ impl<Application: ApplicationLayer> Context<Application> {
return Err(Error::InvalidPacket); return Err(Error::InvalidPacket);
} }
let mut pending = None; let mut incomplete = None;
if let Some(local_session_id) = SessionId::new_from_u64_le(memory::load_raw(incoming_packet)) { if let Some(local_session_id) = SessionId::new_from_u64_le(memory::load_raw(incoming_packet)) {
if let Some(session) = self.look_up_session(local_session_id) { if let Some(session) = self.look_up_session(local_session_id) {
session session
.header_check_cipher .header_protection_cipher
.decrypt_block_in_place(&mut incoming_packet[HEADER_CHECK_ENCRYPT_START..HEADER_CHECK_ENCRYPT_END]); .decrypt_block_in_place(&mut incoming_packet[HEADER_PROTECT_ENCRYPT_START..HEADER_PROTECT_ENCRYPT_END]);
let (key_index, packet_type, fragment_count, fragment_no, counter) = parse_packet_header(&incoming_packet); let (key_index, packet_type, fragment_count, fragment_no, counter) = parse_packet_header(&incoming_packet);
if session.check_receive_window(counter) { if session.check_receive_window(counter) {
@ -327,6 +347,7 @@ impl<Application: ApplicationLayer> Context<Application> {
app, app,
&mut send, &mut send,
&mut check_allow_incoming_session, &mut check_allow_incoming_session,
&mut check_accept_session,
data_buf, data_buf,
counter, counter,
assembled_packet.as_ref(), assembled_packet.as_ref(),
@ -348,6 +369,7 @@ impl<Application: ApplicationLayer> Context<Application> {
app, app,
&mut send, &mut send,
&mut check_allow_incoming_session, &mut check_allow_incoming_session,
&mut check_accept_session,
data_buf, data_buf,
counter, counter,
&[incoming_packet_buf], &[incoming_packet_buf],
@ -360,15 +382,15 @@ impl<Application: ApplicationLayer> Context<Application> {
); );
} }
} else { } else {
return Err(Error::OutOfCounterWindow); return Err(Error::OutOfSequence);
} }
} else { } else {
if let Some(p) = self.sessions.read().unwrap().incomplete.get(&local_session_id).cloned() { if let Some(p) = self.sessions.read().unwrap().incomplete.get(&local_session_id).cloned() {
Aes::new(p.header_check_cipher_key.as_bytes()) Aes::new(p.header_protection_key.as_bytes())
.decrypt_block_in_place(&mut incoming_packet[HEADER_CHECK_ENCRYPT_START..HEADER_CHECK_ENCRYPT_END]); .decrypt_block_in_place(&mut incoming_packet[HEADER_PROTECT_ENCRYPT_START..HEADER_PROTECT_ENCRYPT_END]);
pending = Some(p); incomplete = Some(p);
} else { } else {
return Err(Error::UnknownLocalSessionId(local_session_id)); return Err(Error::UnknownLocalSessionId);
} }
} }
} }
@ -386,12 +408,13 @@ impl<Application: ApplicationLayer> Context<Application> {
app, app,
&mut send, &mut send,
&mut check_allow_incoming_session, &mut check_allow_incoming_session,
&mut check_accept_session,
data_buf, data_buf,
counter, counter,
assembled_packet.as_ref(), assembled_packet.as_ref(),
packet_type, packet_type,
None, None,
pending, incomplete,
key_index, key_index,
mtu, mtu,
current_time, current_time,
@ -402,12 +425,13 @@ impl<Application: ApplicationLayer> Context<Application> {
app, app,
&mut send, &mut send,
&mut check_allow_incoming_session, &mut check_allow_incoming_session,
&mut check_accept_session,
data_buf, data_buf,
counter, counter,
&[incoming_packet_buf], &[incoming_packet_buf],
packet_type, packet_type,
None, None,
pending, incomplete,
key_index, key_index,
mtu, mtu,
current_time, current_time,
@ -421,30 +445,32 @@ impl<Application: ApplicationLayer> Context<Application> {
'b, 'b,
SendFunction: FnMut(Option<&Arc<Session<Application>>>, &mut [u8]), SendFunction: FnMut(Option<&Arc<Session<Application>>>, &mut [u8]),
CheckAllowIncomingSession: FnMut() -> bool, CheckAllowIncomingSession: FnMut() -> bool,
CheckAcceptSession: FnMut(&[u8], Option<&[u8]>) -> Option<(P384PublicKey, Secret<64>, Application::Data)>,
>( >(
&self, &self,
app: &Application, app: &Application,
send: &mut SendFunction, send: &mut SendFunction,
check_allow_incoming_session: &mut CheckAllowIncomingSession, check_allow_incoming_session: &mut CheckAllowIncomingSession,
check_accept_session: &mut CheckAcceptSession,
data_buf: &'b mut [u8], data_buf: &'b mut [u8],
counter: u64, counter: u64,
fragments: &[Application::IncomingPacketBuffer], fragments: &[Application::IncomingPacketBuffer],
packet_type: u8, packet_type: u8,
session: Option<Arc<Session<Application>>>, session: Option<Arc<Session<Application>>>,
pending: Option<Arc<NoiseXKIncoming>>, incomplete: Option<Arc<NoiseXKIncoming>>,
key_index: usize, key_index: usize,
mtu: usize, mtu: usize,
current_time: i64, current_time: i64,
) -> Result<ReceiveResult<'b, Application>, Error> { ) -> Result<ReceiveResult<'b, Application>, Error> {
debug_assert!(fragments.len() >= 1); debug_assert!(fragments.len() >= 1);
let message_nonce = create_message_nonce(packet_type, counter); let incoming_message_nonce = create_message_nonce(packet_type, counter);
if packet_type == PACKET_TYPE_DATA { if packet_type == PACKET_TYPE_DATA {
if let Some(session) = session { if let Some(session) = session {
let state = session.state.read().unwrap(); let state = session.state.read().unwrap();
if let Some(session_key) = state.keys[key_index].as_ref() { if let Some(key) = state.keys[key_index].as_ref() {
let mut c = session_key.get_receive_cipher(); let mut c = key.get_receive_cipher();
c.reset_init_gcm(&message_nonce); c.reset_init_gcm(&incoming_message_nonce);
let mut data_len = 0; let mut data_len = 0;
@ -455,7 +481,7 @@ impl<Application: ApplicationLayer> Context<Application> {
let current_frag_data_start = data_len; let current_frag_data_start = data_len;
data_len += f.len() - HEADER_SIZE; data_len += f.len() - HEADER_SIZE;
if data_len > data_buf.len() { if data_len > data_buf.len() {
session_key.return_receive_cipher(c); key.return_receive_cipher(c);
return Err(Error::DataBufferTooSmall); return Err(Error::DataBufferTooSmall);
} }
c.crypt(&f[HEADER_SIZE..], &mut data_buf[current_frag_data_start..data_len]); c.crypt(&f[HEADER_SIZE..], &mut data_buf[current_frag_data_start..data_len]);
@ -469,7 +495,7 @@ impl<Application: ApplicationLayer> Context<Application> {
} }
data_len += last_fragment.len() - (HEADER_SIZE + AES_GCM_TAG_SIZE); data_len += last_fragment.len() - (HEADER_SIZE + AES_GCM_TAG_SIZE);
if data_len > data_buf.len() { if data_len > data_buf.len() {
session_key.return_receive_cipher(c); key.return_receive_cipher(c);
return Err(Error::DataBufferTooSmall); return Err(Error::DataBufferTooSmall);
} }
let payload_end = last_fragment.len() - AES_GCM_TAG_SIZE; let payload_end = last_fragment.len() - AES_GCM_TAG_SIZE;
@ -479,17 +505,17 @@ impl<Application: ApplicationLayer> Context<Application> {
); );
let aead_authentication_ok = c.finish_decrypt(&last_fragment[payload_end..]); let aead_authentication_ok = c.finish_decrypt(&last_fragment[payload_end..]);
session_key.return_receive_cipher(c); key.return_receive_cipher(c);
if aead_authentication_ok { if aead_authentication_ok {
if session.update_receive_window(counter) { if session.update_receive_window(counter) {
// If the packet authenticated, this confirms that the other side indeed // If the packet authenticated, this confirms that the other side indeed
// knows this session key. In that case mark the session key as confirmed // knows this session key. In that case mark the session key as confirmed
// and if the current active key is older switch it to point to this one. // and if the current active key is older switch it to point to this one.
if session_key.confirmed { if key.confirmed {
drop(state); drop(state);
} else { } else {
let key_created_at_counter = session_key.created_at_counter; let key_created_at_counter = key.created_at_counter;
drop(state); drop(state);
let mut state = session.state.write().unwrap(); let mut state = session.state.write().unwrap();
@ -507,14 +533,14 @@ impl<Application: ApplicationLayer> Context<Application> {
return Ok(ReceiveResult::OkData(session, &mut data_buf[..data_len])); return Ok(ReceiveResult::OkData(session, &mut data_buf[..data_len]));
} else { } else {
return Err(Error::OutOfCounterWindow); return Err(Error::OutOfSequence);
} }
} }
} }
return Err(Error::FailedAuthentication); return Err(Error::FailedAuthentication);
} else { } else {
return Err(Error::SessionNotEstablished); return Err(Error::UnknownLocalSessionId);
} }
} else { } else {
// For Noise setup/KEX packets go ahead and pre-assemble all fragments to simplify the code below. // For Noise setup/KEX packets go ahead and pre-assemble all fragments to simplify the code below.
@ -547,8 +573,8 @@ impl<Application: ApplicationLayer> Context<Application> {
* to the current exchange. * to the current exchange.
*/ */
if session.is_some() || counter != 1 { if session.is_some() || incomplete.is_some() || counter != 1 {
return Err(Error::OutOfCounterWindow); return Err(Error::OutOfSequence);
} }
let pkt: &AliceNoiseXKInit = byte_array_as_proto_buffer(pkt_assembled)?; let pkt: &AliceNoiseXKInit = byte_array_as_proto_buffer(pkt_assembled)?;
@ -560,7 +586,7 @@ impl<Application: ApplicationLayer> Context<Application> {
&pkt.hmac_es, &pkt.hmac_es,
&hmac_sha384_2( &hmac_sha384_2(
kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION>(noise_es.as_bytes()).as_bytes(), kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION>(noise_es.as_bytes()).as_bytes(),
&message_nonce, &incoming_message_nonce,
&pkt_assembled[HEADER_SIZE..AliceNoiseXKInit::AUTH_START], &pkt_assembled[HEADER_SIZE..AliceNoiseXKInit::AUTH_START],
), ),
) { ) {
@ -610,10 +636,10 @@ impl<Application: ApplicationLayer> Context<Application> {
if sessions.incomplete.len() >= self.max_incomplete_session_queue_size { if sessions.incomplete.len() >= self.max_incomplete_session_queue_size {
// If this queue is too big, we remove the latest entry and replace it. The latest // If this queue is too big, we remove the latest entry and replace it. The latest
// is used because under flood conditions this is most likely to be another bogus // is used because under flood conditions this is most likely to be another bogus
// entry. If we find one that is actually timed out, that always gets replaced. // entry. If we find one that is actually timed out, that one is replaced instead.
let mut newest = i64::MIN; let mut newest = i64::MIN;
let mut replace_id = None; let mut replace_id = None;
let cutoff_time = current_time - INCOMPLETE_SESSION_TIMEOUT; let cutoff_time = current_time - Application::SESSION_NEGOTIATION_TIMEOUT_MS;
for (id, s) in sessions.incomplete.iter() { for (id, s) in sessions.incomplete.iter() {
if s.timestamp <= cutoff_time { if s.timestamp <= cutoff_time {
replace_id = Some(*id); replace_id = Some(*id);
@ -635,7 +661,7 @@ impl<Application: ApplicationLayer> Context<Application> {
noise_es_ee: noise_es_ee.clone(), noise_es_ee: noise_es_ee.clone(),
hk, hk,
bob_noise_e_secret, bob_noise_e_secret,
header_check_cipher_key: Secret(pkt.header_check_cipher_key), header_protection_key: Secret(pkt.header_protection_key),
}), }),
); );
@ -672,7 +698,7 @@ impl<Application: ApplicationLayer> Context<Application> {
Some(alice_session_id), Some(alice_session_id),
0, 0,
1, 1,
Some(&Aes::new(&pkt.header_check_cipher_key)), Some(&Aes::new(&pkt.header_protection_key)),
)?; )?;
return Ok(ReceiveResult::Ok); return Ok(ReceiveResult::Ok);
@ -687,13 +713,13 @@ impl<Application: ApplicationLayer> Context<Application> {
* the negotiation. * the negotiation.
*/ */
if counter != 1 { if counter != 1 || incomplete.is_some() {
return Err(Error::OutOfCounterWindow); return Err(Error::OutOfSequence);
} }
if let Some(session) = session { if let Some(session) = session {
let mut offer = session.offer.lock().unwrap(); let state = session.state.read().unwrap();
if let EphemeralOffer::NoiseXKInit(boxed_offer) = &*offer { if let EphemeralOffer::NoiseXKInit(boxed_offer) = &state.offer {
let (alice_e_secret, metadata, noise_es, alice_hk_secret) = boxed_offer.as_ref(); let (alice_e_secret, metadata, noise_es, alice_hk_secret) = boxed_offer.as_ref();
let pkt: &BobNoiseXKAck = byte_array_as_proto_buffer(pkt_assembled)?; let pkt: &BobNoiseXKAck = byte_array_as_proto_buffer(pkt_assembled)?;
@ -714,7 +740,7 @@ impl<Application: ApplicationLayer> Context<Application> {
&pkt.hmac_es_ee, &pkt.hmac_es_ee,
&hmac_sha384_2( &hmac_sha384_2(
noise_es_ee_kex_hmac_key.as_bytes(), noise_es_ee_kex_hmac_key.as_bytes(),
&message_nonce, &incoming_message_nonce,
&pkt_assembled[HEADER_SIZE..BobNoiseXKAck::AUTH_START], &pkt_assembled[HEADER_SIZE..BobNoiseXKAck::AUTH_START],
), ),
) { ) {
@ -749,6 +775,7 @@ impl<Application: ApplicationLayer> Context<Application> {
kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION>(noise_es_ee_se_hk_psk.as_bytes()); kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION>(noise_es_ee_se_hk_psk.as_bytes());
let reply_counter = session.get_next_outgoing_counter().ok_or(Error::MaxKeyLifetimeExceeded)?; let reply_counter = session.get_next_outgoing_counter().ok_or(Error::MaxKeyLifetimeExceeded)?;
debug_assert_eq!(reply_counter.get(), 2);
let reply_message_nonce = create_message_nonce(PACKET_TYPE_ALICE_NOISE_XK_ACK, reply_counter.get()); let reply_message_nonce = create_message_nonce(PACKET_TYPE_ALICE_NOISE_XK_ACK, reply_counter.get());
// Create reply informing Bob of our static identity now that we've verified Bob and set // Create reply informing Bob of our static identity now that we've verified Bob and set
@ -777,7 +804,7 @@ impl<Application: ApplicationLayer> Context<Application> {
// is a hash of 'hk' making it actually a secret and "borrowing" a little PQ // is a hash of 'hk' making it actually a secret and "borrowing" a little PQ
// forward secrecy for Alice's identity. // forward secrecy for Alice's identity.
let mut ctr = AesCtr::new(noise_es_ee_kex_enc_key.as_bytes()); let mut ctr = AesCtr::new(noise_es_ee_kex_enc_key.as_bytes());
ctr.reset_set_iv(&SHA384::hash(hk.as_bytes())[..AES_CTR_NONCE_SIZE]); ctr.reset_set_iv(&hk.as_bytes()[..AES_CTR_NONCE_SIZE]);
ctr.crypt_in_place(&mut reply_buffer[HEADER_SIZE + 1..reply_len]); ctr.crypt_in_place(&mut reply_buffer[HEADER_SIZE + 1..reply_len]);
// First attach HMAC allowing Bob to verify that this is from the same Alice and to // First attach HMAC allowing Bob to verify that this is from the same Alice and to
@ -801,15 +828,11 @@ impl<Application: ApplicationLayer> Context<Application> {
reply_buffer[reply_len..reply_len + HMAC_SHA384_SIZE].copy_from_slice(&hmac_es_ee_se_hk_psk); reply_buffer[reply_len..reply_len + HMAC_SHA384_SIZE].copy_from_slice(&hmac_es_ee_se_hk_psk);
reply_len += HMAC_SHA384_SIZE; reply_len += HMAC_SHA384_SIZE;
// Clear the offer field since we're finished handling a response to our initial offer. drop(state);
*offer = EphemeralOffer::None;
drop(offer);
// Learn Bob's session ID and the first session key.
{ {
let mut state = session.state.write().unwrap(); let mut state = session.state.write().unwrap();
let _ = state.remote_session_id.insert(bob_session_id); let _ = state.remote_session_id.insert(bob_session_id);
let _ = state.keys[0].insert(SessionKey::new( let _ = state.keys[0].insert(SessionKey::new::<Application>(
noise_es_ee_se_hk_psk, noise_es_ee_se_hk_psk,
current_time, current_time,
reply_counter.get(), reply_counter.get(),
@ -817,6 +840,7 @@ impl<Application: ApplicationLayer> Context<Application> {
false, false,
)); ));
state.current_key = 0; state.current_key = 0;
state.offer = EphemeralOffer::None;
} }
send_with_fragmentation( send_with_fragmentation(
@ -827,7 +851,7 @@ impl<Application: ApplicationLayer> Context<Application> {
Some(bob_session_id), Some(bob_session_id),
0, 0,
reply_counter.get(), reply_counter.get(),
Some(&session.header_check_cipher), Some(&session.header_protection_cipher),
)?; )?;
return Ok(ReceiveResult::Ok); return Ok(ReceiveResult::Ok);
@ -835,10 +859,10 @@ impl<Application: ApplicationLayer> Context<Application> {
return Err(Error::InvalidPacket); return Err(Error::InvalidPacket);
} }
} else { } else {
return Ok(ReceiveResult::Ignored); return Err(Error::OutOfSequence);
} }
} else { } else {
return Err(Error::SessionNotEstablished); return Err(Error::UnknownLocalSessionId);
} }
} }
@ -853,120 +877,245 @@ impl<Application: ApplicationLayer> Context<Application> {
* that Alice must return. * that Alice must return.
*/ */
if session.is_some() { if session.is_some() || counter != 2 {
return Ok(ReceiveResult::Ignored); return Err(Error::OutOfSequence);
}
if pkt_assembled.len() < ALICE_NOISE_XK_ACK_MIN_SIZE {
return Err(Error::InvalidPacket);
} }
/* if let Some(incomplete) = incomplete {
// Restore state from note to self, returning to where we were after Alice's first contact. // Check timeout, negotiations aren't allowed to take longer than this.
let alice_session_id = SessionId::new_from_bytes(&bob_note_to_self.alice_session_id).ok_or(Error::InvalidPacket)?; if (current_time - incomplete.timestamp) > Application::SESSION_NEGOTIATION_TIMEOUT_MS {
let bob_noise_e_secret = P384KeyPair::from_bytes(&bob_note_to_self.bob_noise_e, &bob_note_to_self.bob_noise_e_secret) return Err(Error::UnknownLocalSessionId);
.ok_or(Error::InvalidPacket)?; }
let hk = Secret(bob_note_to_self.hk);
let noise_es_ee = Secret(bob_note_to_self.noise_es_ee);
let header_check_cipher = Aes::new(&bob_note_to_self.header_check_cipher_key);
drop(bob_note_to_self_buffer);
// Authenticate packet with noise_es_ee (first HMAC) before decrypting and parsing static info. // Check the first HMAC to verify against the currently known noise_es_ee key, which verifies
let pkt_assembled_enc_end = pkt_assembled.len() - (HMAC_SHA384_SIZE * 2); // that this reply is part of this session.
let auth_start = pkt_assembled.len() - ALICE_NOISE_XK_ACK_AUTH_SIZE;
if !secure_eq( if !secure_eq(
&pkt_assembled[pkt_assembled_enc_end..pkt_assembled.len() - HMAC_SHA384_SIZE], &pkt_assembled[auth_start..pkt_assembled.len() - HMAC_SHA384_SIZE],
&hmac_sha384_2( &hmac_sha384_2(
&kbkdf512(noise_es_ee.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION).as_bytes()[..HMAC_SHA384_SIZE], kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION>(incomplete.noise_es_ee.as_bytes())
&message_nonce, .as_bytes(),
&pkt_assembled[HEADER_SIZE..pkt_assembled_enc_end], &incoming_message_nonce,
&pkt_assembled[HEADER_SIZE..auth_start],
), ),
) { ) {
return Err(Error::FailedAuthentication); return Err(Error::FailedAuthentication);
} }
// Save a copy of the encrypted unmodified packet for final HMAC. // Make a copy of pkt_assembled so we can check the second HMAC against original ciphertext later.
let mut pkt_saved_for_final_hmac = [0u8; NOISE_MAX_HANDSHAKE_PACKET_SIZE]; let mut pkt_assembly_buffer_copy = [0u8; MAX_NOISE_HANDSHAKE_SIZE];
pkt_saved_for_final_hmac[..pkt_assembled.len()].copy_from_slice(pkt_assembled); pkt_assembly_buffer_copy[..pkt_assembled.len()].copy_from_slice(pkt_assembled);
// Decrypt Alice's static identity and decode. // Decrypt encrypted section so we can finally learn Alice's static identity.
let mut ctr = let mut ctr = AesCtr::new(
AesCtr::new(&kbkdf512(noise_es_ee.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION).as_bytes()[..AES_KEY_SIZE]); kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION>(incomplete.noise_es_ee.as_bytes()).as_bytes(),
ctr.reset_set_iv(&SHA384::hash(hk.as_bytes())[..AES_CTR_NONCE_SIZE]); );
ctr.crypt_in_place(&mut pkt_assembled[ALICE_NOISE_XK_ACK_ENC_START..pkt_assembled_enc_end]); ctr.reset_set_iv(&incomplete.hk.as_bytes()[..AES_CTR_NONCE_SIZE]);
ctr.crypt_in_place(&mut pkt_assembled[ALICE_NOISE_XK_ACK_ENC_START..auth_start]);
let mut alice_static_info = &pkt_assembled[ALICE_NOISE_XK_ACK_ENC_START..pkt_assembled_enc_end]; // Read the static public blob and optional meta-data.
if alice_static_info.len() < 2 { let mut pkt_assembled_ptr = HEADER_SIZE + 1;
let mut pkt_assembled_field_end = pkt_assembled_ptr + 2;
if pkt_assembled_field_end >= pkt_assembled.len() {
return Err(Error::InvalidPacket); return Err(Error::InvalidPacket);
} }
let alice_static_public_blob_len = u16::from_le_bytes(alice_static_info[..2].try_into().unwrap()) as usize; let alice_static_public_blob_size =
alice_static_info = &alice_static_info[2..]; u16::from_le(memory::load_raw::<u16>(&pkt_assembled[pkt_assembled_ptr..pkt_assembled_field_end])) as usize;
if alice_static_info.len() < alice_static_public_blob_len { pkt_assembled_ptr = pkt_assembled_field_end;
pkt_assembled_field_end = pkt_assembled_ptr + alice_static_public_blob_size;
if pkt_assembled_field_end >= pkt_assembled.len() {
return Err(Error::InvalidPacket); return Err(Error::InvalidPacket);
} }
let alice_static_public_blob = &alice_static_info[..alice_static_public_blob_len]; let alice_static_public_blob = &pkt_assembled[pkt_assembled_ptr..pkt_assembled_field_end];
alice_static_info = &alice_static_info[alice_static_public_blob_len..]; pkt_assembled_ptr = pkt_assembled_field_end;
if alice_static_info.len() < 2 { pkt_assembled_field_end = pkt_assembled_ptr + 2;
if pkt_assembled_field_end >= pkt_assembled.len() {
return Err(Error::InvalidPacket); return Err(Error::InvalidPacket);
} }
let meta_data_len = u16::from_le_bytes(alice_static_info[..2].try_into().unwrap()) as usize; let alice_meta_data_size =
alice_static_info = &alice_static_info[2..]; u16::from_le(memory::load_raw::<u16>(&pkt_assembled[pkt_assembled_ptr..pkt_assembled_field_end])) as usize;
if alice_static_info.len() < meta_data_len { pkt_assembled_ptr = pkt_assembled_field_end;
return Err(Error::InvalidPacket); pkt_assembled_field_end = pkt_assembled_ptr + alice_meta_data_size;
} let alice_meta_data = if alice_meta_data_size > 0 {
let meta_data = &alice_static_info[..meta_data_len]; Some(&pkt_assembled[pkt_assembled_ptr..pkt_assembled_field_end])
} else {
None
};
if let Some((bob_session_id, psk, app_data)) = // Check session acceptance and fish Alice's NIST P-384 static public key out of
app.accept_new_session(self, remote_address, alice_static_public_blob, meta_data) // her static public blob.
{ let check_result = check_accept_session(alice_static_public_blob, alice_meta_data);
// Create final Noise_XKpsk3 shared secret on this side. if check_result.is_none() {
self.sessions.write().unwrap().incomplete.remove(&incomplete.bob_session_id);
return Ok(ReceiveResult::Rejected);
}
let (alice_noise_s, psk, application_data) = check_result.unwrap();
// Complete Noise_XKpsk3 on Bob's side.
let noise_es_ee_se_hk_psk = Secret(hmac_sha512( let noise_es_ee_se_hk_psk = Secret(hmac_sha512(
&hmac_sha512( &hmac_sha512(
noise_es_ee.as_bytes(), incomplete.noise_es_ee.as_bytes(),
bob_noise_e_secret incomplete
.agree( .bob_noise_e_secret
&Application::extract_s_public_from_raw(alice_static_public_blob) .agree(&alice_noise_s)
.ok_or(Error::FailedAuthentication)?,
)
.ok_or(Error::FailedAuthentication)? .ok_or(Error::FailedAuthentication)?
.as_bytes(), .as_bytes(),
), ),
&hmac_sha512(psk.as_bytes(), hk.as_bytes()), &hmac_sha512(psk.as_bytes(), incomplete.hk.as_bytes()),
)); ));
// Final authentication with the whole enchelada. // Verify the packet using the final key to verify the whole key exchange.
if !secure_eq( if !secure_eq(
&pkt_assembled[pkt_assembled_enc_end + HMAC_SHA384_SIZE..], &pkt_assembly_buffer_copy[auth_start + HMAC_SHA384_SIZE..pkt_assembled.len()],
&hmac_sha384_2( &hmac_sha384_2(
&kbkdf512(noise_es_ee_se_hk_psk.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION).as_bytes() kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION>(noise_es_ee_se_hk_psk.as_bytes())
[..HMAC_SHA384_SIZE], .as_bytes(),
&message_nonce, &incoming_message_nonce,
&pkt_saved_for_final_hmac[HEADER_SIZE..pkt_assembled_enc_end + HMAC_SHA384_SIZE], &pkt_assembly_buffer_copy[HEADER_SIZE..auth_start],
), ),
) { ) {
return Err(Error::FailedAuthentication); return Err(Error::FailedAuthentication);
} }
return Ok(ReceiveResult::OkNewSession(Arc::new(Session { let session = Arc::new(Session {
id: bob_session_id, id: incomplete.bob_session_id,
application_data: app_data, application_data,
psk, psk,
send_counter: AtomicU64::new(2), // 1 was already used in our first reply send_counter: AtomicU64::new(2), // 1 was already used during negotiation
receive_window: std::array::from_fn(|_| AtomicU64::new(0)), receive_window: std::array::from_fn(|_| AtomicU64::new(0)),
header_check_cipher, header_protection_cipher: Aes::new(incomplete.header_protection_key.as_bytes()),
offer: Mutex::new(EphemeralOffer::None),
state: RwLock::new(State { state: RwLock::new(State {
remote_session_id: Some(alice_session_id), remote_session_id: Some(incomplete.alice_session_id),
keys: [Some(SessionKey::new(noise_es_ee_se_hk_psk, current_time, 2, true, true)), None], keys: [
Some(SessionKey::new::<Application>(noise_es_ee_se_hk_psk, current_time, 2, true, true)),
None,
],
current_key: 0, current_key: 0,
offer: EphemeralOffer::None,
}), }),
defrag: Mutex::new(RingBufferMap::new(random::xorshift64_random() as u32)), defrag: Mutex::new(RingBufferMap::new(random::xorshift64_random() as u32)),
}))); });
{
let mut sessions = self.sessions.write().unwrap();
sessions.incomplete.remove(&incomplete.bob_session_id);
sessions.active.insert(incomplete.bob_session_id, Arc::downgrade(&session));
}
return Ok(ReceiveResult::OkNewSession(session));
} else { } else {
return Err(Error::NewSessionRejected); return Err(Error::UnknownLocalSessionId);
} }
*/
todo!()
} }
PACKET_TYPE_ALICE_REKEY_INIT => todo!(), PACKET_TYPE_ALICE_REKEY_INIT => {
if pkt_assembled.len() != AliceRekeyInit::SIZE {
return Err(Error::InvalidPacket);
}
if let Some(session) = session {
let state = session.state.read().unwrap();
if let Some(key) = state.keys[key_index].as_ref() {
// Only the current "Alice" accepts rekeys initiated by the current "Bob."
if !key.role_is_bob {
let mut c = key.get_receive_cipher();
c.reset_init_gcm(&incoming_message_nonce);
c.crypt_in_place(&mut pkt_assembled[AliceRekeyInit::ENC_START..AliceRekeyInit::AUTH_START]);
let aead_authentication_ok = c.finish_decrypt(&pkt_assembled[AliceRekeyInit::AUTH_START..]);
key.return_receive_cipher(c);
PACKET_TYPE_BOB_REKEY_ACK => todo!(), if aead_authentication_ok {
let pkt: &AliceRekeyInit = byte_array_as_proto_buffer(&pkt_assembled).unwrap();
if let Some(alice_e) = P384PublicKey::from_bytes(&pkt.alice_e) {
let bob_e_secret = P384KeyPair::generate();
let ee = bob_e_secret.agree(&alice_e).ok_or(Error::FailedAuthentication)?;
let next_session_key = Secret(hmac_sha512(key.ratchet_key.as_bytes(), ee.as_bytes()));
let mut reply_buf = [0u8; BobRekeyAck::SIZE];
let reply: &mut BobRekeyAck = byte_array_as_proto_buffer_mut(&mut reply_buf).unwrap();
reply.bob_e = *bob_e_secret.public_key_bytes();
reply.next_key_fingerprint = SHA384::hash(next_session_key.as_bytes());
let counter = session.get_next_outgoing_counter().ok_or(Error::MaxKeyLifetimeExceeded)?;
let mut c = key.get_send_cipher(counter.get())?;
c.reset_init_gcm(&create_message_nonce(PACKET_TYPE_BOB_REKEY_ACK, counter.get()));
c.crypt_in_place(&mut reply_buf[BobRekeyAck::ENC_START..BobRekeyAck::AUTH_START]);
reply_buf[BobRekeyAck::AUTH_START..].copy_from_slice(&c.finish_encrypt());
key.return_send_cipher(c);
send(Some(&session), &mut reply_buf);
drop(state);
let mut state = session.state.write().unwrap();
let _ = state.keys[key_index ^ 1].replace(SessionKey::new::<Application>(
next_session_key,
current_time,
counter.get(),
false,
true,
));
return Ok(ReceiveResult::Ok);
}
}
return Err(Error::FailedAuthentication);
}
}
return Err(Error::OutOfSequence);
} else {
return Err(Error::UnknownLocalSessionId);
}
}
PACKET_TYPE_BOB_REKEY_ACK => {
if pkt_assembled.len() != BobRekeyAck::SIZE {
return Err(Error::InvalidPacket);
}
if let Some(session) = session {
let state = session.state.read().unwrap();
if let EphemeralOffer::RekeyInit(alice_e_secret) = &state.offer {
if let Some(key) = state.keys[key_index].as_ref() {
// Only the current "Bob" initiates rekeys and expects this ACK.
if key.role_is_bob {
let mut c = key.get_receive_cipher();
c.reset_init_gcm(&incoming_message_nonce);
c.crypt_in_place(&mut pkt_assembled[BobRekeyAck::ENC_START..BobRekeyAck::AUTH_START]);
let aead_authentication_ok = c.finish_decrypt(&pkt_assembled[BobRekeyAck::AUTH_START..]);
key.return_receive_cipher(c);
if aead_authentication_ok {
let pkt: &BobRekeyAck = byte_array_as_proto_buffer(&pkt_assembled).unwrap();
if let Some(bob_e) = P384PublicKey::from_bytes(&pkt.bob_e) {
let ee = alice_e_secret.agree(&bob_e).ok_or(Error::FailedAuthentication)?;
let next_session_key = Secret(hmac_sha512(key.ratchet_key.as_bytes(), ee.as_bytes()));
if secure_eq(&pkt.next_key_fingerprint, &SHA384::hash(next_session_key.as_bytes())) {
drop(state);
let mut state = session.state.write().unwrap();
let _ = state.keys[key_index ^ 1].replace(SessionKey::new::<Application>(
next_session_key,
current_time,
counter,
true,
false,
));
state.offer = EphemeralOffer::None;
return Ok(ReceiveResult::Ok);
}
}
}
return Err(Error::FailedAuthentication);
}
}
}
return Err(Error::OutOfSequence);
} else {
return Err(Error::UnknownLocalSessionId);
}
}
_ => { _ => {
return Err(Error::InvalidPacket); return Err(Error::InvalidPacket);
@ -1027,8 +1176,8 @@ impl<Application: ApplicationLayer> Session<Application> {
mtu_sized_buffer[fragment_size..tagged_fragment_size].copy_from_slice(&c.finish_encrypt()); mtu_sized_buffer[fragment_size..tagged_fragment_size].copy_from_slice(&c.finish_encrypt());
fragment_size = tagged_fragment_size; fragment_size = tagged_fragment_size;
} }
self.header_check_cipher self.header_protection_cipher
.encrypt_block_in_place(&mut mtu_sized_buffer[HEADER_CHECK_ENCRYPT_START..HEADER_CHECK_ENCRYPT_END]); .encrypt_block_in_place(&mut mtu_sized_buffer[HEADER_PROTECT_ENCRYPT_START..HEADER_PROTECT_ENCRYPT_END]);
send(&mut mtu_sized_buffer[..fragment_size]); send(&mut mtu_sized_buffer[..fragment_size]);
} }
debug_assert!(data.is_empty()); debug_assert!(data.is_empty());
@ -1047,6 +1196,36 @@ impl<Application: ApplicationLayer> Session<Application> {
state.keys[state.current_key].is_some() state.keys[state.current_key].is_some()
} }
/// Send a rekey init message.
///
/// This is called from the session context's service() method when it's time to rekey.
/// It should only be called when the current key was established in the 'bob' role. This
/// is checked when rekey time is checked.
fn send_rekey<SendFunction: FnMut(&mut [u8])>(&self, mut send: SendFunction) {
let rekey_e = P384KeyPair::generate();
let mut rekey_buf = [0u8; AliceRekeyInit::SIZE];
let pkt: &mut AliceRekeyInit = byte_array_as_proto_buffer_mut(&mut rekey_buf).unwrap();
pkt.alice_e = *rekey_e.public_key_bytes();
let state = self.state.read().unwrap();
if let Some(key) = state.keys[state.current_key].as_ref() {
if let Some(counter) = self.get_next_outgoing_counter() {
if let Ok(mut gcm) = key.get_send_cipher(counter.get()) {
gcm.reset_init_gcm(&create_message_nonce(PACKET_TYPE_ALICE_REKEY_INIT, counter.get()));
gcm.crypt_in_place(&mut rekey_buf[AliceRekeyInit::ENC_START..AliceRekeyInit::AUTH_START]);
rekey_buf[AliceRekeyInit::AUTH_START..].copy_from_slice(&gcm.finish_encrypt());
key.return_send_cipher(gcm);
send(&mut rekey_buf);
drop(state);
self.state.write().unwrap().offer = EphemeralOffer::RekeyInit(rekey_e);
}
}
}
}
/// Get the next outgoing counter value. /// Get the next outgoing counter value.
#[inline(always)] #[inline(always)]
fn get_next_outgoing_counter(&self) -> Option<NonZeroU64> { fn get_next_outgoing_counter(&self) -> Option<NonZeroU64> {
@ -1142,7 +1321,7 @@ fn send_with_fragmentation<SendFunction: FnMut(&mut [u8])>(
recipient_session_id: Option<SessionId>, recipient_session_id: Option<SessionId>,
key_index: usize, key_index: usize,
counter: u64, counter: u64,
header_check_cipher: Option<&Aes>, header_protect_cipher: Option<&Aes>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let packet_len = packet.len(); let packet_len = packet.len();
let recipient_session_id = recipient_session_id.map_or(SessionId::NONE, |s| u64::from(s)); let recipient_session_id = recipient_session_id.map_or(SessionId::NONE, |s| u64::from(s));
@ -1160,8 +1339,8 @@ fn send_with_fragmentation<SendFunction: FnMut(&mut [u8])>(
key_index, key_index,
counter, counter,
); );
if let Some(hcc) = header_check_cipher { if let Some(hcc) = header_protect_cipher {
hcc.encrypt_block_in_place(&mut fragment[6..22]); hcc.encrypt_block_in_place(&mut fragment[HEADER_PROTECT_ENCRYPT_START..HEADER_PROTECT_ENCRYPT_END]);
} }
send(fragment); send(fragment);
fragment_start = fragment_end - HEADER_SIZE; fragment_start = fragment_end - HEADER_SIZE;
@ -1171,7 +1350,13 @@ fn send_with_fragmentation<SendFunction: FnMut(&mut [u8])>(
} }
impl SessionKey { impl SessionKey {
fn new(key: Secret<BASE_KEY_SIZE>, current_time: i64, current_counter: u64, confirmed: bool, role_is_bob: bool) -> Self { fn new<Application: ApplicationLayer>(
key: Secret<BASE_KEY_SIZE>,
current_time: i64,
current_counter: u64,
confirmed: bool,
role_is_bob: bool,
) -> Self {
let a2b = kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB>(key.as_bytes()); let a2b = kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB>(key.as_bytes());
let b2a = kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE>(key.as_bytes()); let b2a = kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE>(key.as_bytes());
let (receive_key, send_key) = if role_is_bob { let (receive_key, send_key) = if role_is_bob {
@ -1186,11 +1371,14 @@ impl SessionKey {
receive_cipher_pool: Mutex::new(Vec::with_capacity(2)), receive_cipher_pool: Mutex::new(Vec::with_capacity(2)),
send_cipher_pool: Mutex::new(Vec::with_capacity(2)), send_cipher_pool: Mutex::new(Vec::with_capacity(2)),
rekey_at_time: current_time rekey_at_time: current_time
.checked_add(REKEY_AFTER_TIME_MS + ((random::xorshift64_random() as u32) % REKEY_AFTER_TIME_MS_MAX_JITTER) as i64) .checked_add(
Application::REKEY_AFTER_TIME_MS
+ ((random::xorshift64_random() as u32) % Application::REKEY_AFTER_TIME_MS_MAX_JITTER) as i64,
)
.unwrap(), .unwrap(),
created_at_counter: current_counter, created_at_counter: current_counter,
rekey_at_counter: current_counter.checked_add(REKEY_AFTER_USES).unwrap(), rekey_at_counter: current_counter.checked_add(Application::REKEY_AFTER_USES).unwrap(),
expire_at_counter: current_counter.checked_add(EXPIRE_AFTER_USES).unwrap(), expire_at_counter: current_counter.checked_add(Application::EXPIRE_AFTER_USES).unwrap(),
confirmed, confirmed,
role_is_bob, role_is_bob,
} }