mirror of
https://github.com/ZeroTier/ZeroTierOne
synced 2025-08-22 22:33:58 -07:00
Add automatic retransmission in the earliest stages of session init.
This commit is contained in:
parent
fb5bdf6eb2
commit
5143aa6a4e
4 changed files with 332 additions and 205 deletions
|
@ -49,6 +49,12 @@ pub trait ApplicationLayer: Sized {
|
||||||
/// over very long distances.
|
/// over very long distances.
|
||||||
const INCOMING_SESSION_NEGOTIATION_TIMEOUT_MS: i64 = 2000;
|
const INCOMING_SESSION_NEGOTIATION_TIMEOUT_MS: i64 = 2000;
|
||||||
|
|
||||||
|
/// Retry interval for outgoing connection initiation or rekey attempts.
|
||||||
|
///
|
||||||
|
/// Retry attepmpts will be no more often than this, but the delay may end up being slightly more
|
||||||
|
/// in some cases depending on where in the cycle the initial attempt falls.
|
||||||
|
const RETRY_INTERVAL: i64 = 500;
|
||||||
|
|
||||||
/// Type for arbitrary opaque object for use by the application that is attached to each session.
|
/// Type for arbitrary opaque object for use by the application that is attached to each session.
|
||||||
type Data;
|
type Data;
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,6 @@ mod zssp;
|
||||||
|
|
||||||
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::proto::{MAX_INIT_PAYLOAD_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};
|
||||||
|
|
|
@ -22,8 +22,8 @@ pub const MIN_PACKET_SIZE: usize = HEADER_SIZE + AES_GCM_TAG_SIZE;
|
||||||
/// Minimum physical MTU for ZSSP to function.
|
/// Minimum physical MTU for ZSSP to function.
|
||||||
pub const MIN_TRANSPORT_MTU: usize = 128;
|
pub const MIN_TRANSPORT_MTU: usize = 128;
|
||||||
|
|
||||||
/// Maximum size of init meta-data objects.
|
/// Maximum combined size of static public blob and metadata.
|
||||||
pub const MAX_METADATA_SIZE: usize = 256;
|
pub const MAX_INIT_PAYLOAD_SIZE: usize = MAX_NOISE_HANDSHAKE_SIZE - ALICE_NOISE_XK_ACK_MIN_SIZE;
|
||||||
|
|
||||||
pub(crate) const SESSION_PROTOCOL_VERSION: u8 = 0x00;
|
pub(crate) const SESSION_PROTOCOL_VERSION: u8 = 0x00;
|
||||||
|
|
||||||
|
|
333
zssp/src/zssp.rs
333
zssp/src/zssp.rs
|
@ -11,11 +11,11 @@
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::num::NonZeroU64;
|
use std::num::NonZeroU64;
|
||||||
use std::sync::atomic::{AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicI64, AtomicU64, Ordering};
|
||||||
use std::sync::{Arc, Mutex, RwLock, Weak};
|
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, SHA384_HASH_SIZE};
|
||||||
use zerotier_crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE, 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};
|
||||||
|
@ -25,7 +25,7 @@ use zerotier_utils::gatherarray::GatherArray;
|
||||||
use zerotier_utils::memory;
|
use zerotier_utils::memory;
|
||||||
use zerotier_utils::ringbuffermap::RingBufferMap;
|
use zerotier_utils::ringbuffermap::RingBufferMap;
|
||||||
|
|
||||||
use pqc_kyber::{KYBER_SECRETKEYBYTES, KYBER_SSBYTES};
|
use pqc_kyber::{KYBER_CIPHERTEXTBYTES, KYBER_SECRETKEYBYTES, KYBER_SSBYTES};
|
||||||
|
|
||||||
use crate::applicationlayer::ApplicationLayer;
|
use crate::applicationlayer::ApplicationLayer;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
|
@ -43,6 +43,15 @@ pub struct Context<Application: ApplicationLayer> {
|
||||||
sessions: RwLock<SessionMaps<Application>>,
|
sessions: RwLock<SessionMaps<Application>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Lookup maps for sessions within a session context.
|
||||||
|
struct SessionMaps<Application: ApplicationLayer> {
|
||||||
|
// Active sessions, automatically closed if the application no longer holds their Arc<>.
|
||||||
|
active: HashMap<SessionId, Weak<Session<Application>>>,
|
||||||
|
|
||||||
|
// Incomplete sessions in the middle of three-phase Noise_XK negotiation, expired after timeout.
|
||||||
|
incomplete: HashMap<SessionId, Arc<IncompleteIncomingSession>>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Result generated by the context packet receive function, with possible payloads.
|
/// Result generated by the context packet receive function, with possible payloads.
|
||||||
pub enum ReceiveResult<'b, Application: ApplicationLayer> {
|
pub enum ReceiveResult<'b, Application: ApplicationLayer> {
|
||||||
/// Packet was valid, but no action needs to be taken.
|
/// Packet was valid, but no action needs to be taken.
|
||||||
|
@ -76,45 +85,45 @@ pub struct Session<Application: ApplicationLayer> {
|
||||||
defrag: Mutex<RingBufferMap<u64, GatherArray<Application::IncomingPacketBuffer, MAX_FRAGMENTS>, 16, 16>>,
|
defrag: Mutex<RingBufferMap<u64, GatherArray<Application::IncomingPacketBuffer, MAX_FRAGMENTS>, 16, 16>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SessionMaps<Application: ApplicationLayer> {
|
/// Most of the mutable parts of a session state.
|
||||||
// Active sessions, automatically closed if the application no longer holds their Arc<>.
|
struct State {
|
||||||
active: HashMap<SessionId, Weak<Session<Application>>>,
|
remote_session_id: Option<SessionId>,
|
||||||
|
keys: [Option<SessionKey>; 2],
|
||||||
// Incomplete sessions in the middle of three-phase Noise_XK negotiation, expired after timeout.
|
current_key: usize,
|
||||||
incomplete: HashMap<SessionId, Arc<NoiseXKIncoming>>,
|
current_offer: Offer,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct NoiseXKIncoming {
|
/// State related to an incoming session not yet fully established.
|
||||||
|
struct IncompleteIncomingSession {
|
||||||
timestamp: i64,
|
timestamp: i64,
|
||||||
|
request_hash: [u8; SHA384_HASH_SIZE],
|
||||||
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>,
|
||||||
|
bob_hk_ciphertext: [u8; KYBER_CIPHERTEXTBYTES],
|
||||||
hk: Secret<KYBER_SSBYTES>,
|
hk: Secret<KYBER_SSBYTES>,
|
||||||
header_protection_key: Secret<AES_HEADER_PROTECTION_KEY_SIZE>,
|
header_protection_key: Secret<AES_HEADER_PROTECTION_KEY_SIZE>,
|
||||||
bob_noise_e_secret: P384KeyPair,
|
bob_noise_e_secret: P384KeyPair,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct NoiseXKOutgoing {
|
/// State related to an outgoing session attempt.
|
||||||
timestamp: i64,
|
struct OutgoingSessionInit {
|
||||||
|
last_retry_time: AtomicI64,
|
||||||
alice_noise_e_secret: P384KeyPair,
|
alice_noise_e_secret: P384KeyPair,
|
||||||
noise_es: Secret<P384_ECDH_SHARED_SECRET_SIZE>,
|
noise_es: Secret<P384_ECDH_SHARED_SECRET_SIZE>,
|
||||||
alice_hk_secret: Secret<KYBER_SECRETKEYBYTES>,
|
alice_hk_secret: Secret<KYBER_SECRETKEYBYTES>,
|
||||||
metadata: Option<ArrayVec<u8, MAX_METADATA_SIZE>>,
|
metadata: Option<ArrayVec<u8, MAX_INIT_PAYLOAD_SIZE>>,
|
||||||
|
init_packet: [u8; AliceNoiseXKInit::SIZE],
|
||||||
}
|
}
|
||||||
|
|
||||||
enum EphemeralOffer {
|
/// Latest outgoing offer, either an outgoing attempt or a rekey attempt.
|
||||||
|
enum Offer {
|
||||||
None,
|
None,
|
||||||
NoiseXKInit(Box<NoiseXKOutgoing>),
|
NoiseXKInit(Box<OutgoingSessionInit>),
|
||||||
RekeyInit(P384KeyPair),
|
RekeyInit(P384KeyPair, [u8; AliceRekeyInit::SIZE], AtomicI64),
|
||||||
}
|
|
||||||
|
|
||||||
struct State {
|
|
||||||
remote_session_id: Option<SessionId>,
|
|
||||||
keys: [Option<SessionKey>; 2],
|
|
||||||
current_key: usize,
|
|
||||||
offer: EphemeralOffer,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An ephemeral session key with expiration info.
|
||||||
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
|
||||||
|
@ -126,12 +135,12 @@ struct SessionKey {
|
||||||
rekey_at_counter: u64, // Rekey at or after this counter
|
rekey_at_counter: u64, // Rekey at or after this counter
|
||||||
expire_at_counter: u64, // Hard error when this counter value is reached or exceeded
|
expire_at_counter: u64, // Hard error when this counter value is reached or exceeded
|
||||||
confirmed: bool, // We have confirmed that the other side has this key
|
confirmed: bool, // We have confirmed that the other side has this key
|
||||||
role_is_bob: bool, // Was this side "Bob" in this exchange?
|
bob: bool, // Was this side "Bob" in this exchange?
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Application: ApplicationLayer> Context<Application> {
|
impl<Application: ApplicationLayer> Context<Application> {
|
||||||
/// Create a new session context.
|
/// Create a new session context.
|
||||||
pub fn new(_: &Application, max_incomplete_session_queue_size: usize) -> Self {
|
pub fn new(max_incomplete_session_queue_size: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
max_incomplete_session_queue_size,
|
max_incomplete_session_queue_size,
|
||||||
initial_offer_defrag: Mutex::new(RingBufferMap::new(random::next_u32_secure())),
|
initial_offer_defrag: Mutex::new(RingBufferMap::new(random::next_u32_secure())),
|
||||||
|
@ -145,28 +154,67 @@ 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<SendFunction: FnMut(&Arc<Session<Application>>, &mut [u8])>(&self, mut send: SendFunction, current_time: i64) -> i64 {
|
///
|
||||||
|
/// * `send` - Function to send packets to remote sessions
|
||||||
|
/// * `mtu` - Physical MTU
|
||||||
|
/// * `current_time` - Current monotonic time in milliseconds
|
||||||
|
pub fn service<SendFunction: FnMut(&Arc<Session<Application>>, &mut [u8])>(
|
||||||
|
&self,
|
||||||
|
mut send: SendFunction,
|
||||||
|
mtu: usize,
|
||||||
|
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 retry_cutoff = current_time - Application::RETRY_INTERVAL;
|
||||||
|
let negotiation_timeout_cutoff = current_time - Application::INCOMING_SESSION_NEGOTIATION_TIMEOUT_MS;
|
||||||
|
|
||||||
{
|
{
|
||||||
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 let Some(session) = s.upgrade() {
|
if let Some(session) = s.upgrade() {
|
||||||
let state = session.state.read().unwrap();
|
let state = session.state.read().unwrap();
|
||||||
|
match &state.current_offer {
|
||||||
|
Offer::None => {
|
||||||
if let Some(key) = state.keys[state.current_key].as_ref() {
|
if let Some(key) = state.keys[state.current_key].as_ref() {
|
||||||
if key.role_is_bob
|
if key.bob
|
||||||
&& (current_time >= key.rekey_at_time || session.send_counter.load(Ordering::Relaxed) >= key.rekey_at_counter)
|
&& (current_time >= key.rekey_at_time
|
||||||
|
|| session.send_counter.load(Ordering::Relaxed) >= key.rekey_at_counter)
|
||||||
{
|
{
|
||||||
session.send_rekey(|b| send(&session, b));
|
session.initiate_rekey(|b| send(&session, b), current_time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Offer::NoiseXKInit(offer) => {
|
||||||
|
if offer.last_retry_time.load(Ordering::Relaxed) < retry_cutoff {
|
||||||
|
offer.last_retry_time.store(current_time, Ordering::Relaxed);
|
||||||
|
let _ = send_with_fragmentation(
|
||||||
|
|b| send(&session, b),
|
||||||
|
&mut (offer.init_packet.clone()),
|
||||||
|
mtu,
|
||||||
|
PACKET_TYPE_ALICE_NOISE_XK_INIT,
|
||||||
|
None,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Offer::RekeyInit(_, rekey_packet, last_retry_time) => {
|
||||||
|
if last_retry_time.load(Ordering::Relaxed) < retry_cutoff {
|
||||||
|
last_retry_time.store(current_time, Ordering::Relaxed);
|
||||||
|
send(&session, &mut (rekey_packet.clone()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
dead_active.push(*id);
|
dead_active.push(*id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (id, p) in sessions.incomplete.iter() {
|
|
||||||
if (p.timestamp - current_time) > Application::INCOMING_SESSION_NEGOTIATION_TIMEOUT_MS {
|
for (id, incomplete) in sessions.incomplete.iter() {
|
||||||
|
if incomplete.timestamp < negotiation_timeout_cutoff {
|
||||||
dead_pending.push(*id);
|
dead_pending.push(*id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -182,20 +230,22 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Application::INCOMING_SESSION_NEGOTIATION_TIMEOUT_MS * 2
|
Application::INCOMING_SESSION_NEGOTIATION_TIMEOUT_MS.min(Application::RETRY_INTERVAL)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new session and send initial packet(s) to other side.
|
/// Create a new session and send initial packet(s) to other side.
|
||||||
///
|
///
|
||||||
|
/// This will return Error::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
|
/// * `app` - Application layer instance
|
||||||
/// * `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
|
|
||||||
/// * `remote_s_public_p384` - Remote side's static public NIST P-384 key
|
/// * `remote_s_public_p384` - Remote side's static public NIST P-384 key
|
||||||
/// * `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
|
/// * `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)]
|
/// * `current_time` - Current monotonic time in milliseconds
|
||||||
pub fn open<SendFunction: FnMut(&mut [u8])>(
|
pub fn open<SendFunction: FnMut(&mut [u8])>(
|
||||||
&self,
|
&self,
|
||||||
app: &Application,
|
app: &Application,
|
||||||
|
@ -207,11 +257,9 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
application_data: Application::Data,
|
application_data: Application::Data,
|
||||||
current_time: i64,
|
current_time: i64,
|
||||||
) -> Result<Arc<Session<Application>>, Error> {
|
) -> Result<Arc<Session<Application>>, Error> {
|
||||||
if let Some(md) = metadata.as_ref() {
|
if (metadata.map(|md| md.len()).unwrap_or(0) + app.get_local_s_public_blob().len()) > MAX_INIT_PAYLOAD_SIZE {
|
||||||
if md.len() > MAX_METADATA_SIZE {
|
|
||||||
return Err(Error::DataTooLarge);
|
return Err(Error::DataTooLarge);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
||||||
|
@ -234,19 +282,20 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
id: local_session_id,
|
id: local_session_id,
|
||||||
application_data,
|
application_data,
|
||||||
psk,
|
psk,
|
||||||
send_counter: AtomicU64::new(2), // 1 is the counter value for this INIT message
|
send_counter: AtomicU64::new(3), // 1 and 2 are reserved for init and final ack
|
||||||
receive_window: std::array::from_fn(|_| AtomicU64::new(0)),
|
receive_window: std::array::from_fn(|_| AtomicU64::new(0)),
|
||||||
header_protection_cipher: Aes::new(&header_protection_key),
|
header_protection_cipher: Aes::new(&header_protection_key),
|
||||||
state: RwLock::new(State {
|
state: RwLock::new(State {
|
||||||
remote_session_id: None,
|
remote_session_id: None,
|
||||||
keys: [None, None],
|
keys: [None, None],
|
||||||
current_key: 0,
|
current_key: 0,
|
||||||
offer: EphemeralOffer::NoiseXKInit(Box::new(NoiseXKOutgoing {
|
current_offer: Offer::NoiseXKInit(Box::new(OutgoingSessionInit {
|
||||||
timestamp: current_time,
|
last_retry_time: AtomicI64::new(current_time),
|
||||||
alice_noise_e_secret,
|
alice_noise_e_secret,
|
||||||
noise_es: noise_es.clone(),
|
noise_es: noise_es.clone(),
|
||||||
alice_hk_secret: Secret(alice_hk_secret.secret),
|
alice_hk_secret: Secret(alice_hk_secret.secret),
|
||||||
metadata: metadata.map(|md| ArrayVec::try_from(md).unwrap()),
|
metadata: metadata.map(|md| ArrayVec::try_from(md).unwrap()),
|
||||||
|
init_packet: [0u8; AliceNoiseXKInit::SIZE],
|
||||||
})),
|
})),
|
||||||
}),
|
}),
|
||||||
defrag: Mutex::new(RingBufferMap::new(random::xorshift64_random() as u32)),
|
defrag: Mutex::new(RingBufferMap::new(random::xorshift64_random() as u32)),
|
||||||
|
@ -257,8 +306,15 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
(local_session_id, session)
|
(local_session_id, session)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut init_buffer = [0u8; AliceNoiseXKInit::SIZE];
|
{
|
||||||
let init: &mut AliceNoiseXKInit = byte_array_as_proto_buffer_mut(&mut init_buffer).unwrap();
|
let mut state = session.state.write().unwrap();
|
||||||
|
let init_packet = if let Offer::NoiseXKInit(offer) = &mut state.current_offer {
|
||||||
|
&mut offer.init_packet
|
||||||
|
} else {
|
||||||
|
panic!();
|
||||||
|
};
|
||||||
|
|
||||||
|
let init: &mut AliceNoiseXKInit = byte_array_as_proto_buffer_mut(init_packet).unwrap();
|
||||||
init.session_protocol_version = SESSION_PROTOCOL_VERSION;
|
init.session_protocol_version = SESSION_PROTOCOL_VERSION;
|
||||||
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();
|
||||||
|
@ -267,16 +323,26 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
|
|
||||||
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..]);
|
||||||
ctr.crypt_in_place(&mut init_buffer[AliceNoiseXKInit::ENC_START..AliceNoiseXKInit::AUTH_START]);
|
ctr.crypt_in_place(&mut init_packet[AliceNoiseXKInit::ENC_START..AliceNoiseXKInit::AUTH_START]);
|
||||||
|
|
||||||
let hmac = hmac_sha384_2(
|
let hmac = 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(),
|
||||||
&create_message_nonce(PACKET_TYPE_ALICE_NOISE_XK_INIT, 1),
|
&create_message_nonce(PACKET_TYPE_ALICE_NOISE_XK_INIT, 1),
|
||||||
&init_buffer[HEADER_SIZE..AliceNoiseXKInit::AUTH_START],
|
&init_packet[HEADER_SIZE..AliceNoiseXKInit::AUTH_START],
|
||||||
);
|
);
|
||||||
init_buffer[AliceNoiseXKInit::AUTH_START..AliceNoiseXKInit::AUTH_START + HMAC_SHA384_SIZE].copy_from_slice(&hmac);
|
init_packet[AliceNoiseXKInit::AUTH_START..AliceNoiseXKInit::AUTH_START + HMAC_SHA384_SIZE].copy_from_slice(&hmac);
|
||||||
|
|
||||||
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_packet.clone()),
|
||||||
|
mtu,
|
||||||
|
PACKET_TYPE_ALICE_NOISE_XK_INIT,
|
||||||
|
None,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
None,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
return Ok(session);
|
return Ok(session);
|
||||||
}
|
}
|
||||||
|
@ -308,7 +374,6 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
/// * `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]
|
|
||||||
pub fn receive<
|
pub fn receive<
|
||||||
'b,
|
'b,
|
||||||
SendFunction: FnMut(Option<&Arc<Session<Application>>>, &mut [u8]),
|
SendFunction: FnMut(Option<&Arc<Session<Application>>>, &mut [u8]),
|
||||||
|
@ -332,7 +397,16 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
|
|
||||||
let mut incomplete = 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
|
||||||
|
.sessions
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.active
|
||||||
|
.get(&local_session_id)
|
||||||
|
.and_then(|s| s.upgrade())
|
||||||
|
{
|
||||||
|
debug_assert!(self.sessions.read().unwrap().incomplete.contains_key(&local_session_id));
|
||||||
|
|
||||||
session
|
session
|
||||||
.header_protection_cipher
|
.header_protection_cipher
|
||||||
.decrypt_block_in_place(&mut incoming_packet[HEADER_PROTECT_ENCRYPT_START..HEADER_PROTECT_ENCRYPT_END]);
|
.decrypt_block_in_place(&mut incoming_packet[HEADER_PROTECT_ENCRYPT_START..HEADER_PROTECT_ENCRYPT_END]);
|
||||||
|
@ -455,18 +529,18 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
check_allow_incoming_session: &mut CheckAllowIncomingSession,
|
check_allow_incoming_session: &mut CheckAllowIncomingSession,
|
||||||
check_accept_session: &mut CheckAcceptSession,
|
check_accept_session: &mut CheckAcceptSession,
|
||||||
data_buf: &'b mut [u8],
|
data_buf: &'b mut [u8],
|
||||||
counter: u64,
|
incoming_counter: u64,
|
||||||
fragments: &[Application::IncomingPacketBuffer],
|
fragments: &[Application::IncomingPacketBuffer],
|
||||||
packet_type: u8,
|
packet_type: u8,
|
||||||
session: Option<Arc<Session<Application>>>,
|
session: Option<Arc<Session<Application>>>,
|
||||||
incomplete: Option<Arc<NoiseXKIncoming>>,
|
incomplete: Option<Arc<IncompleteIncomingSession>>,
|
||||||
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 incoming_message_nonce = create_message_nonce(packet_type, counter);
|
let incoming_message_nonce = create_message_nonce(packet_type, incoming_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();
|
||||||
|
@ -510,7 +584,7 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
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(incoming_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.
|
||||||
|
@ -575,15 +649,39 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
* to the current exchange.
|
* to the current exchange.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if session.is_some() || incomplete.is_some() || counter != 1 {
|
if incoming_counter != 1 || session.is_some() {
|
||||||
return Err(Error::OutOfSequence);
|
return Err(Error::OutOfSequence);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hash the init packet so we can check to see if it's just being retransmitted.
|
||||||
|
let request_hash = SHA384::hash(&pkt_assembled);
|
||||||
|
|
||||||
|
let (alice_session_id, bob_session_id, noise_es_ee, bob_hk_ciphertext, header_protection_key, bob_noise_e) =
|
||||||
|
if let Some(incomplete) = incomplete {
|
||||||
|
// If we already have an incoming incomplete session record and the hash matches, recall the
|
||||||
|
// previous state so we can send an identical reply in response to a retransmit.
|
||||||
|
|
||||||
|
if secure_eq(&request_hash, &incomplete.request_hash) {
|
||||||
|
(
|
||||||
|
incomplete.alice_session_id,
|
||||||
|
incomplete.bob_session_id,
|
||||||
|
incomplete.noise_es_ee.clone(),
|
||||||
|
incomplete.bob_hk_ciphertext,
|
||||||
|
incomplete.header_protection_key.clone(),
|
||||||
|
*incomplete.bob_noise_e_secret.public_key_bytes(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return Err(Error::FailedAuthentication);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Otherwise parse the packet, authenticate, generate keys, etc. and record state in an
|
||||||
|
// incomplete state object until this phase of the negotiation is done.
|
||||||
|
|
||||||
let pkt: &AliceNoiseXKInit = byte_array_as_proto_buffer(pkt_assembled)?;
|
let pkt: &AliceNoiseXKInit = byte_array_as_proto_buffer(pkt_assembled)?;
|
||||||
let alice_noise_e = P384PublicKey::from_bytes(&pkt.alice_noise_e).ok_or(Error::FailedAuthentication)?;
|
let alice_noise_e = P384PublicKey::from_bytes(&pkt.alice_noise_e).ok_or(Error::FailedAuthentication)?;
|
||||||
let noise_es = app.get_local_s_keypair().agree(&alice_noise_e).ok_or(Error::FailedAuthentication)?;
|
let noise_es = app.get_local_s_keypair().agree(&alice_noise_e).ok_or(Error::FailedAuthentication)?;
|
||||||
|
|
||||||
// Authenticate packet and prove that Alice knows our static public key.
|
// Authenticate packet and also prove that Alice knows our static public key.
|
||||||
if !secure_eq(
|
if !secure_eq(
|
||||||
&pkt.hmac_es,
|
&pkt.hmac_es,
|
||||||
&hmac_sha384_2(
|
&hmac_sha384_2(
|
||||||
|
@ -601,7 +699,8 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decrypt encrypted part of payload.
|
// Decrypt encrypted part of payload.
|
||||||
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(&SHA384::hash(&pkt.alice_noise_e)[..AES_CTR_NONCE_SIZE]);
|
ctr.reset_set_iv(&SHA384::hash(&pkt.alice_noise_e)[..AES_CTR_NONCE_SIZE]);
|
||||||
ctr.crypt_in_place(&mut pkt_assembled[AliceNoiseXKInit::ENC_START..AliceNoiseXKInit::AUTH_START]);
|
ctr.crypt_in_place(&mut pkt_assembled[AliceNoiseXKInit::ENC_START..AliceNoiseXKInit::AUTH_START]);
|
||||||
|
|
||||||
|
@ -619,12 +718,11 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
.ok_or(Error::FailedAuthentication)?
|
.ok_or(Error::FailedAuthentication)?
|
||||||
.as_bytes(),
|
.as_bytes(),
|
||||||
));
|
));
|
||||||
let (bob_hk_ciphertext, hk) = pqc_kyber::encapsulate(&pkt.alice_hk_public, &mut random::SecureRandom::default())
|
let (bob_hk_ciphertext, hk) =
|
||||||
|
pqc_kyber::encapsulate(&pkt.alice_hk_public, &mut random::SecureRandom::default())
|
||||||
.map_err(|_| Error::FailedAuthentication)
|
.map_err(|_| Error::FailedAuthentication)
|
||||||
.map(|(ct, hk)| (ct, Secret(hk)))?;
|
.map(|(ct, hk)| (ct, Secret(hk)))?;
|
||||||
|
|
||||||
// Pick a session ID for our side and save the intermediate ephemeral state for this exchange.
|
|
||||||
let bob_session_id = {
|
|
||||||
let mut sessions = self.sessions.write().unwrap();
|
let mut sessions = self.sessions.write().unwrap();
|
||||||
|
|
||||||
let mut bob_session_id;
|
let mut bob_session_id;
|
||||||
|
@ -654,53 +752,63 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
let _ = sessions.incomplete.remove(replace_id.as_ref().unwrap());
|
let _ = sessions.incomplete.remove(replace_id.as_ref().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reserve session ID on this side and record incomplete session state.
|
||||||
sessions.incomplete.insert(
|
sessions.incomplete.insert(
|
||||||
bob_session_id,
|
bob_session_id,
|
||||||
Arc::new(NoiseXKIncoming {
|
Arc::new(IncompleteIncomingSession {
|
||||||
timestamp: current_time,
|
timestamp: current_time,
|
||||||
|
request_hash,
|
||||||
alice_session_id,
|
alice_session_id,
|
||||||
bob_session_id,
|
bob_session_id,
|
||||||
noise_es_ee: noise_es_ee.clone(),
|
noise_es_ee: noise_es_ee.clone(),
|
||||||
|
bob_hk_ciphertext,
|
||||||
hk,
|
hk,
|
||||||
bob_noise_e_secret,
|
bob_noise_e_secret,
|
||||||
header_protection_key: Secret(pkt.header_protection_key),
|
header_protection_key: Secret(pkt.header_protection_key),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
bob_session_id
|
(
|
||||||
|
alice_session_id,
|
||||||
|
bob_session_id,
|
||||||
|
noise_es_ee,
|
||||||
|
bob_hk_ciphertext,
|
||||||
|
Secret(pkt.header_protection_key),
|
||||||
|
bob_noise_e,
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create Bob's ephemeral counter-offer reply.
|
// Create Bob's ephemeral counter-offer reply.
|
||||||
let mut reply_buffer = [0u8; BobNoiseXKAck::SIZE];
|
let mut ack_packet = [0u8; BobNoiseXKAck::SIZE];
|
||||||
let reply: &mut BobNoiseXKAck = byte_array_as_proto_buffer_mut(&mut reply_buffer)?;
|
let ack: &mut BobNoiseXKAck = byte_array_as_proto_buffer_mut(&mut ack_packet)?;
|
||||||
reply.session_protocol_version = SESSION_PROTOCOL_VERSION;
|
ack.session_protocol_version = SESSION_PROTOCOL_VERSION;
|
||||||
reply.bob_noise_e = bob_noise_e;
|
ack.bob_noise_e = bob_noise_e;
|
||||||
reply.bob_session_id = *bob_session_id.as_bytes();
|
ack.bob_session_id = *bob_session_id.as_bytes();
|
||||||
reply.bob_hk_ciphertext = bob_hk_ciphertext;
|
ack.bob_hk_ciphertext = bob_hk_ciphertext;
|
||||||
|
|
||||||
// Encrypt main section of reply. Technically we could get away without this but why not?
|
// Encrypt main section of reply. Technically we could get away without this but why not?
|
||||||
let mut ctr =
|
let mut ctr =
|
||||||
AesCtr::new(kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION>(noise_es_ee.as_bytes()).as_bytes());
|
AesCtr::new(kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION>(noise_es_ee.as_bytes()).as_bytes());
|
||||||
ctr.reset_set_iv(&bob_noise_e[P384_PUBLIC_KEY_SIZE - AES_CTR_NONCE_SIZE..]);
|
ctr.reset_set_iv(&bob_noise_e[P384_PUBLIC_KEY_SIZE - AES_CTR_NONCE_SIZE..]);
|
||||||
ctr.crypt_in_place(&mut reply_buffer[BobNoiseXKAck::ENC_START..BobNoiseXKAck::AUTH_START]);
|
ctr.crypt_in_place(&mut ack_packet[BobNoiseXKAck::ENC_START..BobNoiseXKAck::AUTH_START]);
|
||||||
|
|
||||||
// Add HMAC-SHA384 to reply packet.
|
// Add HMAC-SHA384 to reply packet.
|
||||||
let reply_hmac = hmac_sha384_2(
|
let reply_hmac = hmac_sha384_2(
|
||||||
kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION>(noise_es_ee.as_bytes()).as_bytes(),
|
kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION>(noise_es_ee.as_bytes()).as_bytes(),
|
||||||
&create_message_nonce(PACKET_TYPE_BOB_NOISE_XK_ACK, 1),
|
&create_message_nonce(PACKET_TYPE_BOB_NOISE_XK_ACK, 1),
|
||||||
&reply_buffer[HEADER_SIZE..BobNoiseXKAck::AUTH_START],
|
&ack_packet[HEADER_SIZE..BobNoiseXKAck::AUTH_START],
|
||||||
);
|
);
|
||||||
reply_buffer[BobNoiseXKAck::AUTH_START..].copy_from_slice(&reply_hmac);
|
ack_packet[BobNoiseXKAck::AUTH_START..].copy_from_slice(&reply_hmac);
|
||||||
|
|
||||||
send_with_fragmentation(
|
send_with_fragmentation(
|
||||||
|b| send(None, b),
|
|b| send(None, b),
|
||||||
&mut reply_buffer,
|
&mut ack_packet,
|
||||||
mtu,
|
mtu,
|
||||||
PACKET_TYPE_BOB_NOISE_XK_ACK,
|
PACKET_TYPE_BOB_NOISE_XK_ACK,
|
||||||
Some(alice_session_id),
|
Some(alice_session_id),
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
Some(&Aes::new(&pkt.header_protection_key)),
|
Some(&Aes::new(header_protection_key.as_bytes())),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
return Ok(ReceiveResult::Ok);
|
return Ok(ReceiveResult::Ok);
|
||||||
|
@ -715,13 +823,13 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
* the negotiation.
|
* the negotiation.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if counter != 1 || incomplete.is_some() {
|
if incoming_counter != 1 || incomplete.is_some() {
|
||||||
return Err(Error::OutOfSequence);
|
return Err(Error::OutOfSequence);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(session) = session {
|
if let Some(session) = session {
|
||||||
let state = session.state.read().unwrap();
|
let state = session.state.read().unwrap();
|
||||||
if let EphemeralOffer::NoiseXKInit(outgoing_offer) = &state.offer {
|
if let Offer::NoiseXKInit(outgoing_offer) = &state.current_offer {
|
||||||
let pkt: &BobNoiseXKAck = byte_array_as_proto_buffer(pkt_assembled)?;
|
let pkt: &BobNoiseXKAck = byte_array_as_proto_buffer(pkt_assembled)?;
|
||||||
|
|
||||||
if let Some(bob_session_id) = SessionId::new_from_bytes(&pkt.bob_session_id) {
|
if let Some(bob_session_id) = SessionId::new_from_bytes(&pkt.bob_session_id) {
|
||||||
|
@ -776,12 +884,7 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
&hmac_sha512(session.psk.as_bytes(), hk.as_bytes()),
|
&hmac_sha512(session.psk.as_bytes(), hk.as_bytes()),
|
||||||
));
|
));
|
||||||
|
|
||||||
let noise_es_ee_se_hk_psk_hmac_key =
|
let reply_message_nonce = create_message_nonce(PACKET_TYPE_ALICE_NOISE_XK_ACK, 2);
|
||||||
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)?;
|
|
||||||
debug_assert_eq!(reply_counter.get(), 2);
|
|
||||||
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
|
||||||
// up forward secrecy. Also return Bob's opaque note.
|
// up forward secrecy. Also return Bob's opaque note.
|
||||||
|
@ -826,7 +929,8 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
// key exchange. Bob won't be able to do this until he decrypts and parses Alice's
|
// key exchange. Bob won't be able to do this until he decrypts and parses Alice's
|
||||||
// identity, so the first HMAC is to let him authenticate that first.
|
// identity, so the first HMAC is to let him authenticate that first.
|
||||||
let hmac_es_ee_se_hk_psk = hmac_sha384_2(
|
let hmac_es_ee_se_hk_psk = hmac_sha384_2(
|
||||||
noise_es_ee_se_hk_psk_hmac_key.as_bytes(),
|
kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION>(noise_es_ee_se_hk_psk.as_bytes())
|
||||||
|
.as_bytes(),
|
||||||
&reply_message_nonce,
|
&reply_message_nonce,
|
||||||
&reply_buffer[HEADER_SIZE..reply_len],
|
&reply_buffer[HEADER_SIZE..reply_len],
|
||||||
);
|
);
|
||||||
|
@ -840,12 +944,12 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
let _ = state.keys[0].insert(SessionKey::new::<Application>(
|
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(),
|
2,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
));
|
));
|
||||||
state.current_key = 0;
|
state.current_key = 0;
|
||||||
state.offer = EphemeralOffer::None;
|
state.current_offer = Offer::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
send_with_fragmentation(
|
send_with_fragmentation(
|
||||||
|
@ -855,7 +959,7 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
PACKET_TYPE_ALICE_NOISE_XK_ACK,
|
PACKET_TYPE_ALICE_NOISE_XK_ACK,
|
||||||
Some(bob_session_id),
|
Some(bob_session_id),
|
||||||
0,
|
0,
|
||||||
reply_counter.get(),
|
2,
|
||||||
Some(&session.header_protection_cipher),
|
Some(&session.header_protection_cipher),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
@ -882,7 +986,7 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
* that Alice must return.
|
* that Alice must return.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if session.is_some() || counter != 2 {
|
if incoming_counter != 2 || session.is_some() {
|
||||||
return Err(Error::OutOfSequence);
|
return Err(Error::OutOfSequence);
|
||||||
}
|
}
|
||||||
if pkt_assembled.len() < ALICE_NOISE_XK_ACK_MIN_SIZE {
|
if pkt_assembled.len() < ALICE_NOISE_XK_ACK_MIN_SIZE {
|
||||||
|
@ -999,11 +1103,12 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
None,
|
None,
|
||||||
],
|
],
|
||||||
current_key: 0,
|
current_key: 0,
|
||||||
offer: EphemeralOffer::None,
|
current_offer: Offer::None,
|
||||||
}),
|
}),
|
||||||
defrag: Mutex::new(RingBufferMap::new(random::xorshift64_random() as u32)),
|
defrag: Mutex::new(RingBufferMap::new(random::xorshift64_random() as u32)),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Promote this from an incomplete session to an established session.
|
||||||
{
|
{
|
||||||
let mut sessions = self.sessions.write().unwrap();
|
let mut sessions = self.sessions.write().unwrap();
|
||||||
sessions.incomplete.remove(&incomplete.bob_session_id);
|
sessions.incomplete.remove(&incomplete.bob_session_id);
|
||||||
|
@ -1020,11 +1125,15 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
if pkt_assembled.len() != AliceRekeyInit::SIZE {
|
if pkt_assembled.len() != AliceRekeyInit::SIZE {
|
||||||
return Err(Error::InvalidPacket);
|
return Err(Error::InvalidPacket);
|
||||||
}
|
}
|
||||||
|
if incomplete.is_some() {
|
||||||
|
return Err(Error::OutOfSequence);
|
||||||
|
}
|
||||||
|
|
||||||
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(key) = state.keys[key_index].as_ref() {
|
if let Some(key) = state.keys[key_index].as_ref() {
|
||||||
// Only the current "Alice" accepts rekeys initiated by the current "Bob."
|
// Only the current "Alice" accepts rekeys initiated by the current "Bob."
|
||||||
if !key.role_is_bob {
|
if !key.bob {
|
||||||
let mut c = key.get_receive_cipher();
|
let mut c = key.get_receive_cipher();
|
||||||
c.reset_init_gcm(&incoming_message_nonce);
|
c.reset_init_gcm(&incoming_message_nonce);
|
||||||
c.crypt_in_place(&mut pkt_assembled[AliceRekeyInit::ENC_START..AliceRekeyInit::AUTH_START]);
|
c.crypt_in_place(&mut pkt_assembled[AliceRekeyInit::ENC_START..AliceRekeyInit::AUTH_START]);
|
||||||
|
@ -1078,12 +1187,16 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
if pkt_assembled.len() != BobRekeyAck::SIZE {
|
if pkt_assembled.len() != BobRekeyAck::SIZE {
|
||||||
return Err(Error::InvalidPacket);
|
return Err(Error::InvalidPacket);
|
||||||
}
|
}
|
||||||
|
if incomplete.is_some() {
|
||||||
|
return Err(Error::OutOfSequence);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(session) = session {
|
if let Some(session) = session {
|
||||||
let state = session.state.read().unwrap();
|
let state = session.state.read().unwrap();
|
||||||
if let EphemeralOffer::RekeyInit(alice_e_secret) = &state.offer {
|
if let Offer::RekeyInit(alice_e_secret, _, _) = &state.current_offer {
|
||||||
if let Some(key) = state.keys[key_index].as_ref() {
|
if let Some(key) = state.keys[key_index].as_ref() {
|
||||||
// Only the current "Bob" initiates rekeys and expects this ACK.
|
// Only the current "Bob" initiates rekeys and expects this ACK.
|
||||||
if key.role_is_bob {
|
if key.bob {
|
||||||
let mut c = key.get_receive_cipher();
|
let mut c = key.get_receive_cipher();
|
||||||
c.reset_init_gcm(&incoming_message_nonce);
|
c.reset_init_gcm(&incoming_message_nonce);
|
||||||
c.crypt_in_place(&mut pkt_assembled[BobRekeyAck::ENC_START..BobRekeyAck::AUTH_START]);
|
c.crypt_in_place(&mut pkt_assembled[BobRekeyAck::ENC_START..BobRekeyAck::AUTH_START]);
|
||||||
|
@ -1102,11 +1215,11 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
let _ = state.keys[key_index ^ 1].replace(SessionKey::new::<Application>(
|
let _ = state.keys[key_index ^ 1].replace(SessionKey::new::<Application>(
|
||||||
next_session_key,
|
next_session_key,
|
||||||
current_time,
|
current_time,
|
||||||
counter,
|
session.send_counter.load(Ordering::Acquire),
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
));
|
));
|
||||||
state.offer = EphemeralOffer::None;
|
state.current_offer = Offer::None;
|
||||||
|
|
||||||
return Ok(ReceiveResult::Ok);
|
return Ok(ReceiveResult::Ok);
|
||||||
}
|
}
|
||||||
|
@ -1128,11 +1241,6 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Look up a session by local session ID.
|
|
||||||
fn look_up_session(&self, id: SessionId) -> Option<Arc<Session<Application>>> {
|
|
||||||
self.sessions.read().unwrap().active.get(&id).and_then(|s| s.upgrade())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Application: ApplicationLayer> Session<Application> {
|
impl<Application: ApplicationLayer> Session<Application> {
|
||||||
|
@ -1206,7 +1314,7 @@ impl<Application: ApplicationLayer> Session<Application> {
|
||||||
/// This is called from the session context's service() method when it's time to rekey.
|
/// 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
|
/// It should only be called when the current key was established in the 'bob' role. This
|
||||||
/// is checked when rekey time is checked.
|
/// is checked when rekey time is checked.
|
||||||
fn send_rekey<SendFunction: FnMut(&mut [u8])>(&self, mut send: SendFunction) {
|
fn initiate_rekey<SendFunction: FnMut(&mut [u8])>(&self, mut send: SendFunction, current_time: i64) {
|
||||||
let rekey_e = P384KeyPair::generate();
|
let rekey_e = P384KeyPair::generate();
|
||||||
|
|
||||||
let mut rekey_buf = [0u8; AliceRekeyInit::SIZE];
|
let mut rekey_buf = [0u8; AliceRekeyInit::SIZE];
|
||||||
|
@ -1214,6 +1322,7 @@ impl<Application: ApplicationLayer> Session<Application> {
|
||||||
pkt.alice_e = *rekey_e.public_key_bytes();
|
pkt.alice_e = *rekey_e.public_key_bytes();
|
||||||
|
|
||||||
let state = self.state.read().unwrap();
|
let state = self.state.read().unwrap();
|
||||||
|
if let Some(remote_session_id) = state.remote_session_id {
|
||||||
if let Some(key) = state.keys[state.current_key].as_ref() {
|
if let Some(key) = state.keys[state.current_key].as_ref() {
|
||||||
if let Some(counter) = self.get_next_outgoing_counter() {
|
if let Some(counter) = self.get_next_outgoing_counter() {
|
||||||
if let Ok(mut gcm) = key.get_send_cipher(counter.get()) {
|
if let Ok(mut gcm) = key.get_send_cipher(counter.get()) {
|
||||||
|
@ -1222,10 +1331,22 @@ impl<Application: ApplicationLayer> Session<Application> {
|
||||||
rekey_buf[AliceRekeyInit::AUTH_START..].copy_from_slice(&gcm.finish_encrypt());
|
rekey_buf[AliceRekeyInit::AUTH_START..].copy_from_slice(&gcm.finish_encrypt());
|
||||||
key.return_send_cipher(gcm);
|
key.return_send_cipher(gcm);
|
||||||
|
|
||||||
|
debug_assert!(rekey_buf.len() <= MIN_TRANSPORT_MTU);
|
||||||
|
set_packet_header(
|
||||||
|
&mut rekey_buf,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
PACKET_TYPE_ALICE_REKEY_INIT,
|
||||||
|
u64::from(remote_session_id),
|
||||||
|
state.current_key,
|
||||||
|
counter.get(),
|
||||||
|
);
|
||||||
|
|
||||||
send(&mut rekey_buf);
|
send(&mut rekey_buf);
|
||||||
|
|
||||||
drop(state);
|
drop(state);
|
||||||
self.state.write().unwrap().offer = EphemeralOffer::RekeyInit(rekey_e);
|
self.state.write().unwrap().current_offer = Offer::RekeyInit(rekey_e, rekey_buf, AtomicI64::new(current_time));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1240,16 +1361,16 @@ impl<Application: ApplicationLayer> Session<Application> {
|
||||||
/// Check the receive window without mutating state.
|
/// Check the receive window without mutating state.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn check_receive_window(&self, counter: u64) -> bool {
|
fn check_receive_window(&self, counter: u64) -> bool {
|
||||||
let c = self.receive_window[(counter as usize) % COUNTER_WINDOW_MAX_OOO].load(Ordering::Acquire);
|
let prev_counter = self.receive_window[(counter as usize) % COUNTER_WINDOW_MAX_OOO].load(Ordering::Acquire);
|
||||||
c < counter && counter.wrapping_sub(c) < COUNTER_WINDOW_MAX_SKIP_AHEAD
|
prev_counter < counter && counter.wrapping_sub(prev_counter) < COUNTER_WINDOW_MAX_SKIP_AHEAD
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the receive window, returning true if the packet is still valid.
|
/// Update the receive window, returning true if the packet is still valid.
|
||||||
/// This should only be called after the packet is authenticated.
|
/// This should only be called after the packet is authenticated.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn update_receive_window(&self, counter: u64) -> bool {
|
fn update_receive_window(&self, counter: u64) -> bool {
|
||||||
let c = self.receive_window[(counter as usize) % COUNTER_WINDOW_MAX_OOO].fetch_max(counter, Ordering::AcqRel);
|
let prev_counter = self.receive_window[(counter as usize) % COUNTER_WINDOW_MAX_OOO].fetch_max(counter, Ordering::AcqRel);
|
||||||
c < counter && counter.wrapping_sub(c) < COUNTER_WINDOW_MAX_SKIP_AHEAD
|
prev_counter < counter && counter.wrapping_sub(prev_counter) < COUNTER_WINDOW_MAX_SKIP_AHEAD
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1258,7 +1379,7 @@ fn set_packet_header(
|
||||||
fragment_count: usize,
|
fragment_count: usize,
|
||||||
fragment_no: usize,
|
fragment_no: usize,
|
||||||
packet_type: u8,
|
packet_type: u8,
|
||||||
recipient_session_id: u64,
|
remote_session_id: u64,
|
||||||
key_index: usize,
|
key_index: usize,
|
||||||
counter: u64,
|
counter: u64,
|
||||||
) {
|
) {
|
||||||
|
@ -1276,7 +1397,7 @@ fn set_packet_header(
|
||||||
// [58-63] fragment number (0..63)
|
// [58-63] fragment number (0..63)
|
||||||
// [64-127] 64-bit counter
|
// [64-127] 64-bit counter
|
||||||
memory::store_raw(
|
memory::store_raw(
|
||||||
(u64::from(recipient_session_id)
|
(u64::from(remote_session_id)
|
||||||
| ((key_index & 1) as u64).wrapping_shl(48)
|
| ((key_index & 1) as u64).wrapping_shl(48)
|
||||||
| (packet_type as u64).wrapping_shl(49)
|
| (packet_type as u64).wrapping_shl(49)
|
||||||
| ((fragment_count - 1) as u64).wrapping_shl(52)
|
| ((fragment_count - 1) as u64).wrapping_shl(52)
|
||||||
|
@ -1323,13 +1444,13 @@ fn send_with_fragmentation<SendFunction: FnMut(&mut [u8])>(
|
||||||
packet: &mut [u8],
|
packet: &mut [u8],
|
||||||
mtu: usize,
|
mtu: usize,
|
||||||
packet_type: u8,
|
packet_type: u8,
|
||||||
recipient_session_id: Option<SessionId>,
|
remote_session_id: Option<SessionId>,
|
||||||
key_index: usize,
|
key_index: usize,
|
||||||
counter: u64,
|
counter: u64,
|
||||||
header_protect_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 = remote_session_id.map_or(SessionId::NONE, |s| u64::from(s));
|
||||||
let fragment_count = ((packet_len as f32) / (mtu as f32)).ceil() as usize;
|
let fragment_count = ((packet_len as f32) / (mtu as f32)).ceil() as usize;
|
||||||
let mut fragment_start = 0;
|
let mut fragment_start = 0;
|
||||||
let mut fragment_end = packet_len.min(mtu);
|
let mut fragment_end = packet_len.min(mtu);
|
||||||
|
@ -1385,7 +1506,7 @@ impl SessionKey {
|
||||||
rekey_at_counter: current_counter.checked_add(Application::REKEY_AFTER_USES).unwrap(),
|
rekey_at_counter: current_counter.checked_add(Application::REKEY_AFTER_USES).unwrap(),
|
||||||
expire_at_counter: current_counter.checked_add(Application::EXPIRE_AFTER_USES).unwrap(),
|
expire_at_counter: current_counter.checked_add(Application::EXPIRE_AFTER_USES).unwrap(),
|
||||||
confirmed,
|
confirmed,
|
||||||
role_is_bob,
|
bob: role_is_bob,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue