mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-06-08 13:33:44 +02:00
Prep for new controller, also reorg header fields in Noise session and move some functions around.
This commit is contained in:
parent
f153d43797
commit
a723403fe9
5 changed files with 384 additions and 414 deletions
|
@ -57,6 +57,9 @@ ignored when analyzing the "real" security of the protocol.
|
|||
/// Minimum packet size / minimum size for work buffers.
|
||||
pub const MIN_BUFFER_SIZE: usize = 1400;
|
||||
|
||||
/// Minimum possible packet size.
|
||||
pub const MIN_PACKET_SIZE: usize = HEADER_SIZE + 1 + AES_GCM_TAG_SIZE;
|
||||
|
||||
/// Start attempting to rekey after a key has been used to send packets this many times.
|
||||
const REKEY_AFTER_USES: u64 = 536870912;
|
||||
|
||||
|
@ -86,9 +89,7 @@ const E1_TYPE_NONE: u8 = 0;
|
|||
/// Secondary (hybrid) ephemeral key is Kyber512
|
||||
const E1_TYPE_KYBER512: u8 = 1;
|
||||
|
||||
/// Header size; header is: [4] counter | [6] destination session ID | [1] type
|
||||
const HEADER_SIZE: usize = 11;
|
||||
|
||||
const AES_GCM_TAG_SIZE: usize = 16;
|
||||
const HMAC_SIZE: usize = 48; // HMAC-SHA384
|
||||
const SESSION_ID_SIZE: usize = 6;
|
||||
|
@ -128,6 +129,9 @@ pub enum Error {
|
|||
|
||||
/// Attempt to send using session without established key.
|
||||
SessionNotEstablished,
|
||||
|
||||
/// Packet ignored by rate limiter.
|
||||
RateLimited,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
|
@ -140,6 +144,7 @@ impl std::fmt::Display for Error {
|
|||
Self::NewSessionRejected => f.write_str("NewSessionRejected"),
|
||||
Self::MaxKeyLifetimeExceeded => f.write_str("MaxKeyLifetimeExceeded"),
|
||||
Self::SessionNotEstablished => f.write_str("SessionNotEstablished"),
|
||||
Self::RateLimited => f.write_str("RateLimited"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -242,21 +247,61 @@ pub struct Session<O> {
|
|||
psk: Secret<64>,
|
||||
ss: Secret<48>,
|
||||
outgoing_obfuscator: Obfuscator,
|
||||
state: RwLock<State>,
|
||||
state: RwLock<MutableState>,
|
||||
remote_s_public_p384: [u8; P384_PUBLIC_KEY_SIZE],
|
||||
|
||||
/// Arbitrary object associated with this session
|
||||
pub associated_object: O,
|
||||
}
|
||||
|
||||
/// Mutable inner state of Session (except counter, which is atomic to avoid locks on packet sends)
|
||||
struct State {
|
||||
struct MutableState {
|
||||
remote_session_id: Option<SessionId>,
|
||||
keys: [Option<SessionKey>; 2], // current, next
|
||||
offer: Option<EphemeralOffer>,
|
||||
}
|
||||
|
||||
impl<O> Session<O> {
|
||||
/// Create a new session and return this plus an outgoing packet to send to the other end.
|
||||
pub fn new<'a, const MAX_PACKET_SIZE: usize, const STATIC_PUBLIC_SIZE: usize>(
|
||||
buffer: &'a mut [u8; MAX_PACKET_SIZE],
|
||||
local_session_id: SessionId,
|
||||
local_s_public: &[u8; STATIC_PUBLIC_SIZE],
|
||||
local_s_keypair_p384: &P384KeyPair,
|
||||
remote_s_public: &[u8; STATIC_PUBLIC_SIZE],
|
||||
remote_s_public_p384: &P384PublicKey,
|
||||
psk: &Secret<64>,
|
||||
associated_object: O,
|
||||
current_time: i64,
|
||||
jedi: bool,
|
||||
) -> Result<(Self, &'a [u8]), Error> {
|
||||
debug_assert!(MAX_PACKET_SIZE >= MIN_BUFFER_SIZE);
|
||||
let counter = Counter::new();
|
||||
if let Some(ss) = local_s_keypair_p384.agree(remote_s_public_p384) {
|
||||
let outgoing_obfuscator = Obfuscator::new(remote_s_public);
|
||||
if let Some((offer, psize)) = EphemeralOffer::create_alice_offer(buffer, counter.next(), local_session_id, None, local_s_public, remote_s_public_p384, &ss, &outgoing_obfuscator, current_time, jedi) {
|
||||
return Ok((
|
||||
Session::<O> {
|
||||
id: local_session_id,
|
||||
send_counter: counter,
|
||||
remote_s_public_hash: SHA384::hash(remote_s_public),
|
||||
psk: psk.clone(),
|
||||
ss,
|
||||
outgoing_obfuscator,
|
||||
state: RwLock::new(MutableState {
|
||||
remote_session_id: None,
|
||||
keys: [None, None],
|
||||
offer: Some(offer),
|
||||
}),
|
||||
remote_s_public_p384: remote_s_public_p384.as_bytes().clone(),
|
||||
associated_object,
|
||||
},
|
||||
&buffer[..psize],
|
||||
));
|
||||
}
|
||||
}
|
||||
return Err(Error::InvalidParameter);
|
||||
}
|
||||
|
||||
/// Check whether this session should initiate a re-key, returning a packet to send if true.
|
||||
///
|
||||
/// This must be checked often enough to ensure that the hard key usage limit is not reached, which in the
|
||||
|
@ -304,352 +349,319 @@ impl<O> Session<O> {
|
|||
Err(Error::SessionNotEstablished)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new session and return this plus an outgoing packet to send to the other end.
|
||||
pub fn new_session<'a, O, const MAX_PACKET_SIZE: usize, const STATIC_PUBLIC_SIZE: usize>(
|
||||
buffer: &'a mut [u8; MAX_PACKET_SIZE],
|
||||
local_session_id: SessionId,
|
||||
local_s_public: &[u8; STATIC_PUBLIC_SIZE],
|
||||
local_s_keypair_p384: &P384KeyPair,
|
||||
remote_s_public: &[u8; STATIC_PUBLIC_SIZE],
|
||||
remote_s_public_p384: &P384PublicKey,
|
||||
psk: &Secret<64>,
|
||||
associated_object: O,
|
||||
current_time: i64,
|
||||
jedi: bool,
|
||||
) -> Result<(Session<O>, &'a [u8]), Error> {
|
||||
debug_assert!(MAX_PACKET_SIZE >= MIN_BUFFER_SIZE);
|
||||
let counter = Counter::new();
|
||||
if let Some(ss) = local_s_keypair_p384.agree(remote_s_public_p384) {
|
||||
let outgoing_obfuscator = Obfuscator::new(remote_s_public);
|
||||
if let Some((offer, psize)) = EphemeralOffer::create_alice_offer(buffer, counter.next(), local_session_id, None, local_s_public, remote_s_public_p384, &ss, &outgoing_obfuscator, current_time, jedi) {
|
||||
return Ok((
|
||||
Session::<O> {
|
||||
id: local_session_id,
|
||||
send_counter: counter,
|
||||
remote_s_public_hash: SHA384::hash(remote_s_public),
|
||||
psk: psk.clone(),
|
||||
ss,
|
||||
outgoing_obfuscator,
|
||||
state: RwLock::new(State {
|
||||
remote_session_id: None,
|
||||
keys: [None, None],
|
||||
offer: Some(offer),
|
||||
}),
|
||||
remote_s_public_p384: remote_s_public_p384.as_bytes().clone(),
|
||||
associated_object,
|
||||
},
|
||||
&buffer[..psize],
|
||||
));
|
||||
/// Receive a packet from the network and take the appropriate action.
|
||||
///
|
||||
/// Check ReceiveResult to see if it includes data or a reply packet.
|
||||
pub fn receive<
|
||||
'a,
|
||||
ExtractP384PublicKeyFunction: FnOnce(&[u8; STATIC_PUBLIC_SIZE]) -> Option<P384PublicKey>,
|
||||
SessionLookupFunction: FnOnce(SessionId) -> Option<S>,
|
||||
NewSessionAuthenticatorFunction: FnOnce(&[u8; STATIC_PUBLIC_SIZE]) -> Option<(SessionId, Secret<64>, O)>,
|
||||
S: std::ops::Deref<Target = Session<O>>,
|
||||
const MAX_PACKET_SIZE: usize,
|
||||
const STATIC_PUBLIC_SIZE: usize,
|
||||
>(
|
||||
incoming_packet: &[u8],
|
||||
buffer: &'a mut [u8; MAX_PACKET_SIZE],
|
||||
local_s_keypair_p384: &P384KeyPair,
|
||||
incoming_obfuscator: &Obfuscator,
|
||||
extract_p384_static_public: ExtractP384PublicKeyFunction,
|
||||
session_lookup: SessionLookupFunction,
|
||||
new_session_auth: NewSessionAuthenticatorFunction,
|
||||
current_time: i64,
|
||||
jedi: bool,
|
||||
) -> Result<ReceiveResult<'a, O>, Error> {
|
||||
debug_assert!(MAX_PACKET_SIZE >= MIN_BUFFER_SIZE);
|
||||
if incoming_packet.len() > MAX_PACKET_SIZE || incoming_packet.len() <= MIN_PACKET_SIZE {
|
||||
unlikely_branch();
|
||||
return Err(Error::InvalidPacket);
|
||||
}
|
||||
}
|
||||
return Err(Error::InvalidParameter);
|
||||
}
|
||||
|
||||
/// Receive a packet from the network and take the appropriate action.
|
||||
///
|
||||
/// Check ReceiveResult to see if it includes data or a reply packet.
|
||||
pub fn receive<
|
||||
'a,
|
||||
ExtractP384PublicKeyFunction: FnOnce(&[u8; STATIC_PUBLIC_SIZE]) -> Option<P384PublicKey>,
|
||||
SessionLookupFunction: FnOnce(SessionId) -> Option<S>,
|
||||
NewSessionAuthenticatorFunction: FnOnce(&[u8; STATIC_PUBLIC_SIZE]) -> Option<(SessionId, Secret<64>, O)>,
|
||||
S: std::ops::Deref<Target = Session<O>>,
|
||||
O,
|
||||
const MAX_PACKET_SIZE: usize,
|
||||
const STATIC_PUBLIC_SIZE: usize,
|
||||
>(
|
||||
incoming_packet: &[u8],
|
||||
buffer: &'a mut [u8; MAX_PACKET_SIZE],
|
||||
local_s_keypair_p384: &P384KeyPair,
|
||||
incoming_obfuscator: &Obfuscator,
|
||||
extract_p384_static_public: ExtractP384PublicKeyFunction,
|
||||
session_lookup: SessionLookupFunction,
|
||||
new_session_auth: NewSessionAuthenticatorFunction,
|
||||
current_time: i64,
|
||||
jedi: bool,
|
||||
) -> Result<ReceiveResult<'a, O>, Error> {
|
||||
debug_assert!(MAX_PACKET_SIZE >= MIN_BUFFER_SIZE);
|
||||
if incoming_packet.len() > MAX_PACKET_SIZE || incoming_packet.len() <= 16 {
|
||||
unlikely_branch();
|
||||
return Err(Error::InvalidPacket);
|
||||
}
|
||||
incoming_obfuscator.0.decrypt_block(&incoming_packet[..16], &mut buffer[..16]);
|
||||
let packet_type = buffer[0];
|
||||
let local_session_id = SessionId::new_from_bytes(&buffer[1..7]);
|
||||
|
||||
incoming_obfuscator.0.decrypt_block(&incoming_packet[0..16], &mut buffer[0..16]);
|
||||
let local_session_id = SessionId::new_from_bytes(&buffer[4..10]);
|
||||
let packet_type = buffer[10];
|
||||
debug_assert_eq!(PACKET_TYPE_DATA, 0);
|
||||
debug_assert_eq!(PACKET_TYPE_NOP, 1);
|
||||
if packet_type <= PACKET_TYPE_NOP {
|
||||
if let Some(local_session_id) = local_session_id {
|
||||
if let Some(session) = session_lookup(local_session_id) {
|
||||
let state = session.state.read();
|
||||
for ki in 0..2 {
|
||||
if let Some(key) = state.keys[ki].as_ref() {
|
||||
let nonce = get_aes_gcm_nonce(buffer);
|
||||
let mut c = key.get_receive_cipher();
|
||||
c.init(&nonce);
|
||||
c.crypt_in_place(&mut buffer[HEADER_SIZE..16]);
|
||||
let data_len = incoming_packet.len() - AES_GCM_TAG_SIZE;
|
||||
c.crypt(&incoming_packet[16..data_len], &mut buffer[16..data_len]);
|
||||
let tag = c.finish();
|
||||
key.return_receive_cipher(c);
|
||||
|
||||
debug_assert_eq!(PACKET_TYPE_DATA, 0);
|
||||
debug_assert_eq!(PACKET_TYPE_NOP, 1);
|
||||
if packet_type <= PACKET_TYPE_NOP {
|
||||
if let Some(local_session_id) = local_session_id {
|
||||
if let Some(session) = session_lookup(local_session_id) {
|
||||
let state = session.state.read();
|
||||
for ki in 0..2 {
|
||||
if let Some(key) = state.keys[ki].as_ref() {
|
||||
let nonce = get_aes_gcm_nonce(buffer);
|
||||
let mut c = key.get_receive_cipher();
|
||||
c.init(&nonce);
|
||||
c.crypt_in_place(&mut buffer[HEADER_SIZE..16]);
|
||||
let data_len = incoming_packet.len() - AES_GCM_TAG_SIZE;
|
||||
c.crypt(&incoming_packet[16..data_len], &mut buffer[16..data_len]);
|
||||
let tag = c.finish();
|
||||
key.return_receive_cipher(c);
|
||||
if tag.eq(&incoming_packet[data_len..]) {
|
||||
if ki == 1 {
|
||||
// Promote next key to current key on success.
|
||||
unlikely_branch();
|
||||
drop(state);
|
||||
let mut state = session.state.write();
|
||||
state.keys[0] = state.keys[1].take();
|
||||
}
|
||||
|
||||
if tag.eq(&incoming_packet[data_len..]) {
|
||||
if ki == 1 {
|
||||
// Promote next key to current key on success.
|
||||
unlikely_branch();
|
||||
drop(state);
|
||||
let mut state = session.state.write();
|
||||
state.keys[0] = state.keys[1].take();
|
||||
}
|
||||
|
||||
if packet_type == PACKET_TYPE_DATA {
|
||||
return Ok(ReceiveResult::OkData(&buffer[HEADER_SIZE..data_len], u32::from_le_bytes(nonce[..4].try_into().unwrap())));
|
||||
} else {
|
||||
return Ok(ReceiveResult::Ok);
|
||||
if packet_type == PACKET_TYPE_DATA {
|
||||
return Ok(ReceiveResult::OkData(&buffer[HEADER_SIZE..data_len], u32::from_le_bytes(nonce[7..11].try_into().unwrap())));
|
||||
} else {
|
||||
return Ok(ReceiveResult::Ok);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Err(Error::FailedAuthentication);
|
||||
} else {
|
||||
unlikely_branch();
|
||||
return Err(Error::UnknownLocalSessionId(Some(local_session_id)));
|
||||
}
|
||||
return Err(Error::FailedAuthentication);
|
||||
} else {
|
||||
unlikely_branch();
|
||||
return Err(Error::UnknownLocalSessionId(Some(local_session_id)));
|
||||
return Err(Error::UnknownLocalSessionId(None));
|
||||
}
|
||||
} else {
|
||||
unlikely_branch();
|
||||
return Err(Error::UnknownLocalSessionId(None));
|
||||
}
|
||||
} else {
|
||||
unlikely_branch();
|
||||
|
||||
let session = if let Some(local_session_id) = local_session_id {
|
||||
let s = session_lookup(local_session_id);
|
||||
if s.is_none() {
|
||||
return Err(Error::UnknownLocalSessionId(Some(local_session_id)));
|
||||
}
|
||||
s
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if incoming_packet.len() > (HEADER_SIZE + P384_PUBLIC_KEY_SIZE + AES_GCM_TAG_SIZE + HMAC_SIZE) {
|
||||
incoming_obfuscator.0.decrypt_block(&incoming_packet[16..32], &mut buffer[16..32]);
|
||||
incoming_obfuscator.0.decrypt_block(&incoming_packet[32..48], &mut buffer[32..48]);
|
||||
incoming_obfuscator.0.decrypt_block(&incoming_packet[48..64], &mut buffer[48..64]);
|
||||
buffer[64..incoming_packet.len()].copy_from_slice(&incoming_packet[64..]);
|
||||
} else {
|
||||
return Err(Error::InvalidPacket);
|
||||
}
|
||||
let payload_end = incoming_packet.len() - (AES_GCM_TAG_SIZE + HMAC_SIZE);
|
||||
let aes_gcm_tag_end = incoming_packet.len() - HMAC_SIZE;
|
||||
|
||||
match packet_type {
|
||||
PACKET_TYPE_KEY_OFFER => {
|
||||
// alice (remote) -> bob (local)
|
||||
|
||||
let (alice_e0_public, e0s) = P384PublicKey::from_bytes(&buffer[HEADER_SIZE..HEADER_SIZE + P384_PUBLIC_KEY_SIZE])
|
||||
.and_then(|pk| local_s_keypair_p384.agree(&pk).map(move |s| (pk, s)))
|
||||
.ok_or(Error::FailedAuthentication)?;
|
||||
|
||||
let key = Secret(hmac_sha512(&hmac_sha512(&KEY_DERIVATION_CHAIN_STARTING_SALT, alice_e0_public.as_bytes()), e0s.as_bytes()));
|
||||
|
||||
let original_ciphertext = buffer.clone();
|
||||
let mut c = AesGcm::new(kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB).first_n::<32>(), false);
|
||||
c.init(&get_aes_gcm_nonce(buffer));
|
||||
c.crypt_in_place(&mut buffer[(HEADER_SIZE + P384_PUBLIC_KEY_SIZE)..payload_end]);
|
||||
let c = c.finish();
|
||||
if !c.eq(&buffer[payload_end..aes_gcm_tag_end]) {
|
||||
return Err(Error::FailedAuthentication);
|
||||
let session = if let Some(local_session_id) = local_session_id {
|
||||
let s = session_lookup(local_session_id);
|
||||
if s.is_none() {
|
||||
return Err(Error::UnknownLocalSessionId(Some(local_session_id)));
|
||||
}
|
||||
s
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let (alice_session_id, alice_s_public, alice_e1_public) = parse_KEY_OFFER_after_header(&buffer[(HEADER_SIZE + P384_PUBLIC_KEY_SIZE)..payload_end])?;
|
||||
if incoming_packet.len() > (HEADER_SIZE + P384_PUBLIC_KEY_SIZE + AES_GCM_TAG_SIZE + HMAC_SIZE) {
|
||||
incoming_obfuscator.0.decrypt_block(&incoming_packet[16..32], &mut buffer[16..32]);
|
||||
incoming_obfuscator.0.decrypt_block(&incoming_packet[32..48], &mut buffer[32..48]);
|
||||
incoming_obfuscator.0.decrypt_block(&incoming_packet[48..64], &mut buffer[48..64]);
|
||||
buffer[64..incoming_packet.len()].copy_from_slice(&incoming_packet[64..]);
|
||||
} else {
|
||||
return Err(Error::InvalidPacket);
|
||||
}
|
||||
let payload_end = incoming_packet.len() - (AES_GCM_TAG_SIZE + HMAC_SIZE);
|
||||
let aes_gcm_tag_end = incoming_packet.len() - HMAC_SIZE;
|
||||
|
||||
if let Some(session) = session.as_ref() {
|
||||
// If we already have a session for this session ID, make sure this is the same node calling.
|
||||
if !session.remote_s_public_hash.eq(&SHA384::hash(&alice_s_public)) {
|
||||
match packet_type {
|
||||
PACKET_TYPE_KEY_OFFER => {
|
||||
// alice (remote) -> bob (local)
|
||||
|
||||
// Check rate limit if this session is known.
|
||||
if let Some(session) = session.as_ref() {
|
||||
if let Some(offer) = session.state.read().offer.as_ref() {
|
||||
if (current_time - offer.creation_time) < OFFER_RATE_LIMIT_MS {
|
||||
return Err(Error::RateLimited);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (alice_e0_public, e0s) = P384PublicKey::from_bytes(&buffer[HEADER_SIZE..HEADER_SIZE + P384_PUBLIC_KEY_SIZE])
|
||||
.and_then(|pk| local_s_keypair_p384.agree(&pk).map(move |s| (pk, s)))
|
||||
.ok_or(Error::FailedAuthentication)?;
|
||||
|
||||
let key = Secret(hmac_sha512(&hmac_sha512(&KEY_DERIVATION_CHAIN_STARTING_SALT, alice_e0_public.as_bytes()), e0s.as_bytes()));
|
||||
|
||||
let original_ciphertext = buffer.clone();
|
||||
let mut c = AesGcm::new(kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB).first_n::<32>(), false);
|
||||
c.init(&get_aes_gcm_nonce(buffer));
|
||||
c.crypt_in_place(&mut buffer[(HEADER_SIZE + P384_PUBLIC_KEY_SIZE)..payload_end]);
|
||||
let c = c.finish();
|
||||
if !c.eq(&buffer[payload_end..aes_gcm_tag_end]) {
|
||||
return Err(Error::FailedAuthentication);
|
||||
}
|
||||
}
|
||||
|
||||
let alice_s_public_p384 = extract_p384_static_public(&alice_s_public).ok_or(Error::InvalidPacket)?;
|
||||
let ss = local_s_keypair_p384.agree(&alice_s_public_p384).ok_or(Error::FailedAuthentication)?;
|
||||
let (alice_session_id, alice_s_public, alice_e1_public) = parse_KEY_OFFER_after_header(&buffer[(HEADER_SIZE + P384_PUBLIC_KEY_SIZE)..payload_end])?;
|
||||
|
||||
let key = Secret(hmac_sha512(key.as_bytes(), ss.as_bytes()));
|
||||
|
||||
if !hmac_sha384(kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_HMAC).first_n::<48>(), &original_ciphertext[..aes_gcm_tag_end]).eq(&buffer[aes_gcm_tag_end..incoming_packet.len()]) {
|
||||
return Err(Error::FailedAuthentication);
|
||||
}
|
||||
|
||||
// Alice's offer has been verified and her current key state reconstructed.
|
||||
|
||||
let bob_e0_keypair = P384KeyPair::generate();
|
||||
let e0e0 = bob_e0_keypair.agree(&alice_e0_public).ok_or(Error::FailedAuthentication)?;
|
||||
let se0 = bob_e0_keypair.agree(&alice_s_public_p384).ok_or(Error::FailedAuthentication)?;
|
||||
|
||||
let new_session = if session.is_some() {
|
||||
None
|
||||
} else {
|
||||
if let Some((local_session_id, psk, associated_object)) = new_session_auth(&alice_s_public) {
|
||||
Some(Session::<O> {
|
||||
id: local_session_id,
|
||||
send_counter: Counter::new(),
|
||||
remote_s_public_hash: SHA384::hash(&alice_s_public),
|
||||
psk,
|
||||
ss,
|
||||
outgoing_obfuscator: Obfuscator::new(&alice_s_public),
|
||||
state: RwLock::new(State {
|
||||
remote_session_id: Some(alice_session_id),
|
||||
keys: [None, None],
|
||||
offer: None,
|
||||
}),
|
||||
remote_s_public_p384: alice_s_public_p384.as_bytes().clone(),
|
||||
associated_object,
|
||||
})
|
||||
} else {
|
||||
return Err(Error::NewSessionRejected);
|
||||
}
|
||||
};
|
||||
let session_ref = session;
|
||||
let session = session_ref.as_ref().map_or_else(|| new_session.as_ref().unwrap(), |s| &*s);
|
||||
|
||||
// FIPS note: the order of HMAC parameters are flipped here from the usual Noise HMAC(key, X). That's because
|
||||
// NIST/FIPS allows HKDF with HMAC(salt, key) and salt is allowed to be anything. This way if the PSK is not
|
||||
// FIPS compliant the compliance of the entire key derivation is not invalidated. Both inputs are secrets of
|
||||
// fixed size so this shouldn't matter cryptographically.
|
||||
let key = Secret(hmac_sha512(
|
||||
session.psk.as_bytes(),
|
||||
&hmac_sha512(&hmac_sha512(&hmac_sha512(key.as_bytes(), bob_e0_keypair.public_key_bytes()), e0e0.as_bytes()), se0.as_bytes()),
|
||||
));
|
||||
|
||||
// At this point we've completed Noise_IK key derivation with NIST P-384 ECDH, but see final step below...
|
||||
|
||||
let (bob_e1_public, e1e1) = if jedi && alice_e1_public.is_some() {
|
||||
if let Ok((bob_e1_public, e1e1)) = pqc_kyber::encapsulate(alice_e1_public.as_ref().unwrap(), &mut random::SecureRandom::default()) {
|
||||
(Some(bob_e1_public), Secret(e1e1))
|
||||
} else {
|
||||
return Err(Error::FailedAuthentication);
|
||||
}
|
||||
} else {
|
||||
(None, Secret::default()) // use all zero Kyber secret if disabled
|
||||
};
|
||||
|
||||
let counter = session.send_counter.next();
|
||||
let mut reply_size = assemble_KEY_COUNTER_OFFER(buffer, counter, alice_session_id, bob_e0_keypair.public_key(), session.id, bob_e1_public.as_ref());
|
||||
|
||||
let mut c = AesGcm::new(kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE).first_n::<32>(), true);
|
||||
c.init(&get_aes_gcm_nonce(buffer));
|
||||
c.crypt_in_place(&mut buffer[(HEADER_SIZE + P384_PUBLIC_KEY_SIZE)..reply_size]);
|
||||
let c = c.finish();
|
||||
buffer[reply_size..(reply_size + AES_GCM_TAG_SIZE)].copy_from_slice(&c);
|
||||
reply_size += AES_GCM_TAG_SIZE;
|
||||
|
||||
// Normal Noise_IK is done, but we have one more step: mix in the Kyber shared secret (or all zeroes if Kyber is
|
||||
// disabled). We have to wait until this point because Kyber's keys are encrypted and can't be decrypted until
|
||||
// the P-384 exchange is done. We also flip the HMAC parameter order here for the same reason we do in the previous
|
||||
// key derivation step.
|
||||
let key = Secret(hmac_sha512(e1e1.as_bytes(), key.as_bytes()));
|
||||
|
||||
let hmac = hmac_sha384(kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_HMAC).first_n::<48>(), &buffer[..reply_size]);
|
||||
buffer[reply_size..reply_size + HMAC_SIZE].copy_from_slice(&hmac);
|
||||
reply_size += HMAC_SIZE;
|
||||
|
||||
let mut state = session.state.write();
|
||||
let _ = state.remote_session_id.replace(alice_session_id);
|
||||
state.keys[1].replace(SessionKey::new(key, Role::Bob, current_time, counter, jedi));
|
||||
drop(state);
|
||||
|
||||
// Bob now has final key state for this exchange. Yay! Now reply to Alice so she can construct it.
|
||||
|
||||
session.outgoing_obfuscator.0.encrypt_block_in_place(&mut buffer[0..16]);
|
||||
session.outgoing_obfuscator.0.encrypt_block_in_place(&mut buffer[16..32]);
|
||||
session.outgoing_obfuscator.0.encrypt_block_in_place(&mut buffer[32..48]);
|
||||
session.outgoing_obfuscator.0.encrypt_block_in_place(&mut buffer[48..64]);
|
||||
|
||||
return new_session.map_or_else(|| Ok(ReceiveResult::OkSendReply(&buffer[..reply_size])), |ns| Ok(ReceiveResult::OkNewSession(ns, &buffer[..reply_size])));
|
||||
}
|
||||
|
||||
PACKET_TYPE_KEY_COUNTER_OFFER => {
|
||||
// bob (remote) -> alice (local)
|
||||
|
||||
if let Some(session) = session {
|
||||
let state = session.state.upgradable_read();
|
||||
if let Some(offer) = state.offer.as_ref() {
|
||||
let (bob_e0_public, e0e0) = P384PublicKey::from_bytes(&buffer[HEADER_SIZE..(HEADER_SIZE + P384_PUBLIC_KEY_SIZE)])
|
||||
.and_then(|pk| offer.alice_e0_keypair.agree(&pk).map(move |s| (pk, s)))
|
||||
.ok_or(Error::FailedAuthentication)?;
|
||||
let se0 = local_s_keypair_p384.agree(&bob_e0_public).ok_or(Error::FailedAuthentication)?;
|
||||
|
||||
let key = Secret(hmac_sha512(
|
||||
session.psk.as_bytes(),
|
||||
&hmac_sha512(&hmac_sha512(&hmac_sha512(offer.key.as_bytes(), bob_e0_public.as_bytes()), e0e0.as_bytes()), se0.as_bytes()),
|
||||
));
|
||||
|
||||
let original_ciphertext = buffer.clone();
|
||||
let mut c = AesGcm::new(kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE).first_n::<32>(), false);
|
||||
c.init(&get_aes_gcm_nonce(buffer));
|
||||
c.crypt_in_place(&mut buffer[(HEADER_SIZE + P384_PUBLIC_KEY_SIZE)..payload_end]);
|
||||
let c = c.finish();
|
||||
if !c.eq(&buffer[payload_end..aes_gcm_tag_end]) {
|
||||
// Important! Check to make sure the caller's public identity matches the one for this session.
|
||||
if let Some(session) = session.as_ref() {
|
||||
if !session.remote_s_public_hash.eq(&SHA384::hash(&alice_s_public)) {
|
||||
return Err(Error::FailedAuthentication);
|
||||
}
|
||||
}
|
||||
|
||||
// Alice has now completed Noise_IK for P-384 and verified with GCM auth, now for the hybrid add-on.
|
||||
let alice_s_public_p384 = extract_p384_static_public(&alice_s_public).ok_or(Error::InvalidPacket)?;
|
||||
let ss = local_s_keypair_p384.agree(&alice_s_public_p384).ok_or(Error::FailedAuthentication)?;
|
||||
|
||||
let (bob_session_id, bob_e1_public) = parse_KEY_COUNTER_OFFER_after_header(&buffer[(HEADER_SIZE + P384_PUBLIC_KEY_SIZE)..payload_end])?;
|
||||
let key = Secret(hmac_sha512(key.as_bytes(), ss.as_bytes()));
|
||||
|
||||
let e1e1 = if jedi && bob_e1_public.is_some() && offer.alice_e1_keypair.is_some() {
|
||||
if let Ok(e1e1) = pqc_kyber::decapsulate(bob_e1_public.as_ref().unwrap(), &offer.alice_e1_keypair.as_ref().unwrap().secret) {
|
||||
Secret(e1e1)
|
||||
} else {
|
||||
if !hmac_sha384(kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_HMAC).first_n::<48>(), &original_ciphertext[..aes_gcm_tag_end]).eq(&buffer[aes_gcm_tag_end..incoming_packet.len()]) {
|
||||
return Err(Error::FailedAuthentication);
|
||||
}
|
||||
|
||||
// Alice's offer has been verified and her current key state reconstructed.
|
||||
|
||||
let bob_e0_keypair = P384KeyPair::generate();
|
||||
let e0e0 = bob_e0_keypair.agree(&alice_e0_public).ok_or(Error::FailedAuthentication)?;
|
||||
let se0 = bob_e0_keypair.agree(&alice_s_public_p384).ok_or(Error::FailedAuthentication)?;
|
||||
|
||||
let new_session = if session.is_some() {
|
||||
None
|
||||
} else {
|
||||
if let Some((local_session_id, psk, associated_object)) = new_session_auth(&alice_s_public) {
|
||||
Some(Session::<O> {
|
||||
id: local_session_id,
|
||||
send_counter: Counter::new(),
|
||||
remote_s_public_hash: SHA384::hash(&alice_s_public),
|
||||
psk,
|
||||
ss,
|
||||
outgoing_obfuscator: Obfuscator::new(&alice_s_public),
|
||||
state: RwLock::new(MutableState {
|
||||
remote_session_id: Some(alice_session_id),
|
||||
keys: [None, None],
|
||||
offer: None,
|
||||
}),
|
||||
remote_s_public_p384: alice_s_public_p384.as_bytes().clone(),
|
||||
associated_object,
|
||||
})
|
||||
} else {
|
||||
return Err(Error::NewSessionRejected);
|
||||
}
|
||||
};
|
||||
let session_ref = session;
|
||||
let session = session_ref.as_ref().map_or_else(|| new_session.as_ref().unwrap(), |s| &*s);
|
||||
|
||||
// FIPS note: the order of HMAC parameters are flipped here from the usual Noise HMAC(key, X). That's because
|
||||
// NIST/FIPS allows HKDF with HMAC(salt, key) and salt is allowed to be anything. This way if the PSK is not
|
||||
// FIPS compliant the compliance of the entire key derivation is not invalidated. Both inputs are secrets of
|
||||
// fixed size so this shouldn't matter cryptographically.
|
||||
let key = Secret(hmac_sha512(
|
||||
session.psk.as_bytes(),
|
||||
&hmac_sha512(&hmac_sha512(&hmac_sha512(key.as_bytes(), bob_e0_keypair.public_key_bytes()), e0e0.as_bytes()), se0.as_bytes()),
|
||||
));
|
||||
|
||||
// At this point we've completed Noise_IK key derivation with NIST P-384 ECDH, but see final step below...
|
||||
|
||||
let (bob_e1_public, e1e1) = if jedi && alice_e1_public.is_some() {
|
||||
if let Ok((bob_e1_public, e1e1)) = pqc_kyber::encapsulate(alice_e1_public.as_ref().unwrap(), &mut random::SecureRandom::default()) {
|
||||
(Some(bob_e1_public), Secret(e1e1))
|
||||
} else {
|
||||
return Err(Error::FailedAuthentication);
|
||||
}
|
||||
} else {
|
||||
(None, Secret::default()) // use all zero Kyber secret if disabled
|
||||
};
|
||||
|
||||
let counter = session.send_counter.next();
|
||||
let mut reply_size = assemble_KEY_COUNTER_OFFER(buffer, counter, alice_session_id, bob_e0_keypair.public_key(), session.id, bob_e1_public.as_ref());
|
||||
|
||||
let mut c = AesGcm::new(kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE).first_n::<32>(), true);
|
||||
c.init(&get_aes_gcm_nonce(buffer));
|
||||
c.crypt_in_place(&mut buffer[(HEADER_SIZE + P384_PUBLIC_KEY_SIZE)..reply_size]);
|
||||
let c = c.finish();
|
||||
buffer[reply_size..(reply_size + AES_GCM_TAG_SIZE)].copy_from_slice(&c);
|
||||
reply_size += AES_GCM_TAG_SIZE;
|
||||
|
||||
// Normal Noise_IK is done, but we have one more step: mix in the Kyber shared secret (or all zeroes if Kyber is
|
||||
// disabled). We have to wait until this point because Kyber's keys are encrypted and can't be decrypted until
|
||||
// the P-384 exchange is done. We also flip the HMAC parameter order here for the same reason we do in the previous
|
||||
// key derivation step.
|
||||
let key = Secret(hmac_sha512(e1e1.as_bytes(), key.as_bytes()));
|
||||
|
||||
let hmac = hmac_sha384(kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_HMAC).first_n::<48>(), &buffer[..reply_size]);
|
||||
buffer[reply_size..reply_size + HMAC_SIZE].copy_from_slice(&hmac);
|
||||
reply_size += HMAC_SIZE;
|
||||
|
||||
let mut state = session.state.write();
|
||||
let _ = state.remote_session_id.replace(alice_session_id);
|
||||
state.keys[1].replace(SessionKey::new(key, Role::Bob, current_time, counter, jedi));
|
||||
drop(state);
|
||||
|
||||
// Bob now has final key state for this exchange. Yay! Now reply to Alice so she can construct it.
|
||||
|
||||
session.outgoing_obfuscator.0.encrypt_block_in_place(&mut buffer[0..16]);
|
||||
session.outgoing_obfuscator.0.encrypt_block_in_place(&mut buffer[16..32]);
|
||||
session.outgoing_obfuscator.0.encrypt_block_in_place(&mut buffer[32..48]);
|
||||
session.outgoing_obfuscator.0.encrypt_block_in_place(&mut buffer[48..64]);
|
||||
|
||||
return new_session.map_or_else(|| Ok(ReceiveResult::OkSendReply(&buffer[..reply_size])), |ns| Ok(ReceiveResult::OkNewSession(ns, &buffer[..reply_size])));
|
||||
}
|
||||
|
||||
PACKET_TYPE_KEY_COUNTER_OFFER => {
|
||||
// bob (remote) -> alice (local)
|
||||
|
||||
if let Some(session) = session {
|
||||
let state = session.state.upgradable_read();
|
||||
if let Some(offer) = state.offer.as_ref() {
|
||||
let (bob_e0_public, e0e0) = P384PublicKey::from_bytes(&buffer[HEADER_SIZE..(HEADER_SIZE + P384_PUBLIC_KEY_SIZE)])
|
||||
.and_then(|pk| offer.alice_e0_keypair.agree(&pk).map(move |s| (pk, s)))
|
||||
.ok_or(Error::FailedAuthentication)?;
|
||||
let se0 = local_s_keypair_p384.agree(&bob_e0_public).ok_or(Error::FailedAuthentication)?;
|
||||
|
||||
let key = Secret(hmac_sha512(
|
||||
session.psk.as_bytes(),
|
||||
&hmac_sha512(&hmac_sha512(&hmac_sha512(offer.key.as_bytes(), bob_e0_public.as_bytes()), e0e0.as_bytes()), se0.as_bytes()),
|
||||
));
|
||||
|
||||
let original_ciphertext = buffer.clone();
|
||||
let mut c = AesGcm::new(kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE).first_n::<32>(), false);
|
||||
c.init(&get_aes_gcm_nonce(buffer));
|
||||
c.crypt_in_place(&mut buffer[(HEADER_SIZE + P384_PUBLIC_KEY_SIZE)..payload_end]);
|
||||
let c = c.finish();
|
||||
if !c.eq(&buffer[payload_end..aes_gcm_tag_end]) {
|
||||
return Err(Error::FailedAuthentication);
|
||||
}
|
||||
} else {
|
||||
Secret::default()
|
||||
};
|
||||
|
||||
let key = Secret(hmac_sha512(e1e1.as_bytes(), key.as_bytes()));
|
||||
// Alice has now completed Noise_IK for P-384 and verified with GCM auth, now for the hybrid add-on.
|
||||
|
||||
if !hmac_sha384(kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_HMAC).first_n::<48>(), &original_ciphertext[..aes_gcm_tag_end]).eq(&buffer[aes_gcm_tag_end..incoming_packet.len()]) {
|
||||
return Err(Error::FailedAuthentication);
|
||||
}
|
||||
let (bob_session_id, bob_e1_public) = parse_KEY_COUNTER_OFFER_after_header(&buffer[(HEADER_SIZE + P384_PUBLIC_KEY_SIZE)..payload_end])?;
|
||||
|
||||
// Alice has now completed and validated the full hybrid exchange. If this is the first exchange send
|
||||
// a NOP back to Bob to acknowledge that the session is open and can now be used. Otherwise just queue
|
||||
// this up as the next key to be promoted to current when Bob uses it.
|
||||
let e1e1 = if jedi && bob_e1_public.is_some() && offer.alice_e1_keypair.is_some() {
|
||||
if let Ok(e1e1) = pqc_kyber::decapsulate(bob_e1_public.as_ref().unwrap(), &offer.alice_e1_keypair.as_ref().unwrap().secret) {
|
||||
Secret(e1e1)
|
||||
} else {
|
||||
return Err(Error::FailedAuthentication);
|
||||
}
|
||||
} else {
|
||||
Secret::default()
|
||||
};
|
||||
|
||||
let mut state = RwLockUpgradableReadGuard::upgrade(state);
|
||||
let _ = state.offer.take();
|
||||
let _ = state.remote_session_id.replace(bob_session_id);
|
||||
if state.keys[0].is_some() {
|
||||
let _ = state.keys[1].replace(SessionKey::new(key, Role::Alice, current_time, session.send_counter.current(), jedi));
|
||||
return Ok(ReceiveResult::Ok);
|
||||
} else {
|
||||
let counter = session.send_counter.next();
|
||||
let key = SessionKey::new(key, Role::Alice, current_time, counter, jedi);
|
||||
let key = Secret(hmac_sha512(e1e1.as_bytes(), key.as_bytes()));
|
||||
|
||||
let dummy_data_len = (random::next_u32_secure() % (MAX_PACKET_SIZE - (HEADER_SIZE + AES_GCM_TAG_SIZE)) as u32) as usize;
|
||||
let mut dummy_data = [0_u8; MAX_PACKET_SIZE];
|
||||
random::fill_bytes_secure(&mut dummy_data[..dummy_data_len]);
|
||||
let nop_len = assemble_and_armor_DATA(buffer, &dummy_data[..dummy_data_len], PACKET_TYPE_NOP, u64::from(bob_session_id), counter, &key, &session.outgoing_obfuscator)?;
|
||||
if !hmac_sha384(kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_HMAC).first_n::<48>(), &original_ciphertext[..aes_gcm_tag_end]).eq(&buffer[aes_gcm_tag_end..incoming_packet.len()]) {
|
||||
return Err(Error::FailedAuthentication);
|
||||
}
|
||||
|
||||
let _ = state.keys[0].replace(key);
|
||||
let _ = state.keys[1].take();
|
||||
// Alice has now completed and validated the full hybrid exchange. If this is the first exchange send
|
||||
// a NOP back to Bob to acknowledge that the session is open and can now be used. Otherwise just queue
|
||||
// this up as the next key to be promoted to current when Bob uses it.
|
||||
|
||||
return Ok(ReceiveResult::OkSendReply(&buffer[..nop_len]));
|
||||
let mut state = RwLockUpgradableReadGuard::upgrade(state);
|
||||
let _ = state.offer.take();
|
||||
let _ = state.remote_session_id.replace(bob_session_id);
|
||||
if state.keys[0].is_some() {
|
||||
let _ = state.keys[1].replace(SessionKey::new(key, Role::Alice, current_time, session.send_counter.current(), jedi));
|
||||
return Ok(ReceiveResult::Ok);
|
||||
} else {
|
||||
let counter = session.send_counter.next();
|
||||
let key = SessionKey::new(key, Role::Alice, current_time, counter, jedi);
|
||||
|
||||
let dummy_data_len = (random::next_u32_secure() % (MAX_PACKET_SIZE - (HEADER_SIZE + AES_GCM_TAG_SIZE)) as u32) as usize;
|
||||
let mut dummy_data = [0_u8; MAX_PACKET_SIZE];
|
||||
random::fill_bytes_secure(&mut dummy_data[..dummy_data_len]);
|
||||
let nop_len = assemble_and_armor_DATA(buffer, &dummy_data[..dummy_data_len], PACKET_TYPE_NOP, u64::from(bob_session_id), counter, &key, &session.outgoing_obfuscator)?;
|
||||
|
||||
let _ = state.keys[0].replace(key);
|
||||
let _ = state.keys[1].take();
|
||||
|
||||
return Ok(ReceiveResult::OkSendReply(&buffer[..nop_len]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Just ignore counter-offers that are out of place. They probably indicate that this side
|
||||
// restarted and needs to establish a new session.
|
||||
return Ok(ReceiveResult::Ignored);
|
||||
}
|
||||
|
||||
// Just ignore counter-offers that are out of place. They probably indicate that this side
|
||||
// restarted and needs to establish a new session.
|
||||
return Ok(ReceiveResult::Ignored);
|
||||
_ => return Err(Error::InvalidPacket),
|
||||
}
|
||||
|
||||
_ => return Err(Error::InvalidPacket),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -845,10 +857,10 @@ impl SessionKey {
|
|||
|
||||
#[allow(non_snake_case)]
|
||||
fn assemble_and_armor_DATA<const MAX_PACKET_SIZE: usize>(buffer: &mut [u8; MAX_PACKET_SIZE], data: &[u8], packet_type: u8, remote_session_id: u64, counter: CounterValue, key: &SessionKey, outgoing_obfuscator: &Obfuscator) -> Result<usize, Error> {
|
||||
buffer[0..4].copy_from_slice(&counter.to_bytes());
|
||||
buffer[4..10].copy_from_slice(&remote_session_id.to_le_bytes()[..SESSION_ID_SIZE]);
|
||||
debug_assert!(packet_type == PACKET_TYPE_DATA || packet_type == PACKET_TYPE_NOP);
|
||||
buffer[10] = packet_type;
|
||||
buffer[0] = packet_type;
|
||||
buffer[1..7].copy_from_slice(&remote_session_id.to_le_bytes()[..SESSION_ID_SIZE]);
|
||||
buffer[7..11].copy_from_slice(&counter.to_bytes());
|
||||
|
||||
let payload_end = HEADER_SIZE + data.len();
|
||||
let tag_end = payload_end + AES_GCM_TAG_SIZE;
|
||||
|
@ -889,9 +901,9 @@ fn assemble_KEY_OFFER<const MAX_PACKET_SIZE: usize, const STATIC_PUBLIC_SIZE: us
|
|||
alice_s_public: &[u8; STATIC_PUBLIC_SIZE],
|
||||
alice_e1_public: Option<&[u8; pqc_kyber::KYBER_PUBLICKEYBYTES]>,
|
||||
) -> usize {
|
||||
buffer[0..4].copy_from_slice(&counter.to_bytes());
|
||||
buffer[4..10].copy_from_slice(&bob_session_id.map_or(0_u64, |i| i.into()).to_le_bytes()[..SESSION_ID_SIZE]);
|
||||
buffer[10] = PACKET_TYPE_KEY_OFFER;
|
||||
buffer[0] = PACKET_TYPE_KEY_OFFER;
|
||||
buffer[1..7].copy_from_slice(&bob_session_id.map_or(0_u64, |i| i.into()).to_le_bytes()[..SESSION_ID_SIZE]);
|
||||
buffer[7..11].copy_from_slice(&counter.to_bytes());
|
||||
let mut b = &mut buffer[HEADER_SIZE..];
|
||||
|
||||
b[..P384_PUBLIC_KEY_SIZE].copy_from_slice(alice_e0_public.as_bytes());
|
||||
|
@ -961,9 +973,9 @@ fn assemble_KEY_COUNTER_OFFER<const MAX_PACKET_SIZE: usize>(
|
|||
bob_session_id: SessionId,
|
||||
bob_e1_public: Option<&[u8; pqc_kyber::KYBER_CIPHERTEXTBYTES]>,
|
||||
) -> usize {
|
||||
buffer[0..4].copy_from_slice(&counter.to_bytes());
|
||||
alice_session_id.copy_to(&mut buffer[4..10]);
|
||||
buffer[10] = PACKET_TYPE_KEY_COUNTER_OFFER;
|
||||
buffer[0] = PACKET_TYPE_KEY_COUNTER_OFFER;
|
||||
alice_session_id.copy_to(&mut buffer[1..7]);
|
||||
buffer[7..11].copy_from_slice(&counter.to_bytes());
|
||||
let mut b = &mut buffer[HEADER_SIZE..];
|
||||
|
||||
b[..P384_PUBLIC_KEY_SIZE].copy_from_slice(bob_e0_public.as_bytes());
|
||||
|
@ -1053,7 +1065,7 @@ mod tests {
|
|||
let mut from_bob: Vec<Vec<u8>> = Vec::new();
|
||||
|
||||
// Session TO Bob, on Alice's side.
|
||||
let (alice, packet) = new_session(
|
||||
let (alice, packet) = Session::new(
|
||||
&mut a_buffer,
|
||||
SessionId::new_random(),
|
||||
alice_static_keypair.public_key_bytes(),
|
||||
|
@ -1075,7 +1087,7 @@ mod tests {
|
|||
for _ in 0..256 {
|
||||
while !from_alice.is_empty() || !from_bob.is_empty() {
|
||||
if let Some(packet) = from_alice.pop() {
|
||||
let r = receive(
|
||||
let r = Session::receive(
|
||||
packet.as_slice(),
|
||||
&mut b_buffer,
|
||||
&bob_static_keypair,
|
||||
|
@ -1138,7 +1150,7 @@ mod tests {
|
|||
}
|
||||
|
||||
if let Some(packet) = from_bob.pop() {
|
||||
let r = receive(
|
||||
let r = Session::receive(
|
||||
packet.as_slice(),
|
||||
&mut b_buffer,
|
||||
&alice_static_keypair,
|
||||
|
|
|
@ -102,11 +102,8 @@ pub trait InnerProtocolInterface: Sync + Send + 'static {
|
|||
/// Handle an OK, returing true if the OK was recognized.
|
||||
async fn handle_ok<SI: SystemInterface>(&self, source: &Peer<SI>, source_path: &Path<SI>, in_re_verb: u8, in_re_message_id: u64, payload: &PacketBuffer, cursor: &mut usize) -> bool;
|
||||
|
||||
/// Check if this remote peer has a trust relationship with this node.
|
||||
///
|
||||
/// This is checked to determine if we should do things like make direct links or respond to
|
||||
/// various other VL1 messages.
|
||||
fn has_trust_relationship(&self, id: &Identity) -> bool;
|
||||
/// Check if this peer should communicate with another at all.
|
||||
fn should_communicate_with(&self, id: &Identity) -> bool;
|
||||
}
|
||||
|
||||
/// How often to check the root cluster definitions against the root list and update.
|
||||
|
|
|
@ -75,13 +75,7 @@ pub struct Peer<SI: SystemInterface> {
|
|||
}
|
||||
|
||||
/// Attempt AEAD packet encryption and MAC validation. Returns message ID on success.
|
||||
fn try_aead_decrypt(
|
||||
secret: &SymmetricSecret,
|
||||
packet_frag0_payload_bytes: &[u8],
|
||||
packet_header: &PacketHeader,
|
||||
fragments: &[Option<PooledPacketBuffer>],
|
||||
payload: &mut PacketBuffer,
|
||||
) -> Option<MessageId> {
|
||||
fn try_aead_decrypt(secret: &SymmetricSecret, packet_frag0_payload_bytes: &[u8], packet_header: &PacketHeader, fragments: &[Option<PooledPacketBuffer>], payload: &mut PacketBuffer) -> Option<MessageId> {
|
||||
let cipher = packet_header.cipher();
|
||||
match cipher {
|
||||
security_constants::CIPHER_NOCRYPT_POLY1305 | security_constants::CIPHER_SALSA2012_POLY1305 => {
|
||||
|
@ -279,13 +273,7 @@ impl<SI: SystemInterface> Peer<SI> {
|
|||
match &p.endpoint {
|
||||
Endpoint::IpUdp(existing_ip) => {
|
||||
if existing_ip.ip_bytes().eq(new_ip.ip_bytes()) {
|
||||
debug_event!(
|
||||
si,
|
||||
"[vl1] {} replacing path {} with {} (same IP, different port)",
|
||||
self.identity.address.to_string(),
|
||||
p.endpoint.to_string(),
|
||||
new_path.endpoint.to_string()
|
||||
);
|
||||
debug_event!(si, "[vl1] {} replacing path {} with {} (same IP, different port)", self.identity.address.to_string(), p.endpoint.to_string(), new_path.endpoint.to_string());
|
||||
pi.path = Arc::downgrade(new_path);
|
||||
pi.canonical_instance_id = new_path.canonical.canonical_instance_id();
|
||||
pi.last_receive_time_ticks = time_ticks;
|
||||
|
@ -332,15 +320,7 @@ impl<SI: SystemInterface> Peer<SI> {
|
|||
///
|
||||
/// This does not set the fragmentation field in the packet header, MAC, or encrypt the packet. The sender
|
||||
/// must do that while building the packet. The fragmentation flag must be set if fragmentation will be needed.
|
||||
async fn internal_send(
|
||||
&self,
|
||||
si: &SI,
|
||||
endpoint: &Endpoint,
|
||||
local_socket: Option<&SI::LocalSocket>,
|
||||
local_interface: Option<&SI::LocalInterface>,
|
||||
max_fragment_size: usize,
|
||||
packet: &PacketBuffer,
|
||||
) -> bool {
|
||||
async fn internal_send(&self, si: &SI, endpoint: &Endpoint, local_socket: Option<&SI::LocalSocket>, local_interface: Option<&SI::LocalInterface>, max_fragment_size: usize, packet: &PacketBuffer) -> bool {
|
||||
let packet_size = packet.len();
|
||||
if packet_size > max_fragment_size {
|
||||
let bytes = packet.as_bytes();
|
||||
|
@ -350,8 +330,7 @@ impl<SI: SystemInterface> Peer<SI> {
|
|||
let mut pos = UDP_DEFAULT_MTU;
|
||||
|
||||
let overrun_size = (packet_size - UDP_DEFAULT_MTU) as u32;
|
||||
let fragment_count =
|
||||
(overrun_size / (UDP_DEFAULT_MTU - packet_constants::FRAGMENT_HEADER_SIZE) as u32) + (((overrun_size % (UDP_DEFAULT_MTU - packet_constants::FRAGMENT_HEADER_SIZE) as u32) != 0) as u32);
|
||||
let fragment_count = (overrun_size / (UDP_DEFAULT_MTU - packet_constants::FRAGMENT_HEADER_SIZE) as u32) + (((overrun_size % (UDP_DEFAULT_MTU - packet_constants::FRAGMENT_HEADER_SIZE) as u32) != 0) as u32);
|
||||
debug_assert!(fragment_count <= packet_constants::FRAGMENT_COUNT_MAX as u32);
|
||||
|
||||
let mut header = FragmentHeader {
|
||||
|
@ -400,9 +379,16 @@ impl<SI: SystemInterface> Peer<SI> {
|
|||
}
|
||||
};
|
||||
|
||||
let max_fragment_size = if path.endpoint.requires_fragmentation() { UDP_DEFAULT_MTU } else { usize::MAX };
|
||||
let flags_cipher_hops =
|
||||
if packet.len() > max_fragment_size { packet_constants::HEADER_FLAG_FRAGMENTED | security_constants::CIPHER_AES_GMAC_SIV } else { security_constants::CIPHER_AES_GMAC_SIV };
|
||||
let max_fragment_size = if path.endpoint.requires_fragmentation() {
|
||||
UDP_DEFAULT_MTU
|
||||
} else {
|
||||
usize::MAX
|
||||
};
|
||||
let flags_cipher_hops = if packet.len() > max_fragment_size {
|
||||
packet_constants::HEADER_FLAG_FRAGMENTED | security_constants::CIPHER_AES_GMAC_SIV
|
||||
} else {
|
||||
security_constants::CIPHER_AES_GMAC_SIV
|
||||
};
|
||||
|
||||
let mut aes_gmac_siv = self.identity_symmetric_key.aes_gmac_siv.get();
|
||||
aes_gmac_siv.encrypt_init(&self.next_message_id().to_ne_bytes());
|
||||
|
@ -481,7 +467,11 @@ impl<SI: SystemInterface> Peer<SI> {
|
|||
}
|
||||
};
|
||||
|
||||
let max_fragment_size = if destination.requires_fragmentation() { UDP_DEFAULT_MTU } else { usize::MAX };
|
||||
let max_fragment_size = if destination.requires_fragmentation() {
|
||||
UDP_DEFAULT_MTU
|
||||
} else {
|
||||
usize::MAX
|
||||
};
|
||||
let time_ticks = si.time_ticks();
|
||||
|
||||
let mut packet = PacketBuffer::new();
|
||||
|
@ -532,17 +522,7 @@ impl<SI: SystemInterface> Peer<SI> {
|
|||
/// those fragments after the main packet header and first chunk.
|
||||
///
|
||||
/// This returns true if the packet decrypted and passed authentication.
|
||||
pub(crate) async fn receive<PH: InnerProtocolInterface>(
|
||||
&self,
|
||||
node: &Node<SI>,
|
||||
si: &SI,
|
||||
ph: &PH,
|
||||
time_ticks: i64,
|
||||
source_path: &Arc<Path<SI>>,
|
||||
packet_header: &PacketHeader,
|
||||
frag0: &PacketBuffer,
|
||||
fragments: &[Option<PooledPacketBuffer>],
|
||||
) -> bool {
|
||||
pub(crate) async fn receive<PH: InnerProtocolInterface>(&self, node: &Node<SI>, si: &SI, ph: &PH, time_ticks: i64, source_path: &Arc<Path<SI>>, packet_header: &PacketHeader, frag0: &PacketBuffer, fragments: &[Option<PooledPacketBuffer>]) -> bool {
|
||||
if let Ok(packet_frag0_payload_bytes) = frag0.as_bytes_starting_at(packet_constants::VERB_INDEX) {
|
||||
let mut payload = PacketBuffer::new();
|
||||
|
||||
|
@ -606,18 +586,8 @@ impl<SI: SystemInterface> Peer<SI> {
|
|||
return false;
|
||||
}
|
||||
|
||||
async fn handle_incoming_hello<PH: InnerProtocolInterface>(
|
||||
&self,
|
||||
si: &SI,
|
||||
ph: &PH,
|
||||
node: &Node<SI>,
|
||||
time_ticks: i64,
|
||||
message_id: MessageId,
|
||||
source_path: &Arc<Path<SI>>,
|
||||
hops: u8,
|
||||
payload: &PacketBuffer,
|
||||
) -> bool {
|
||||
if !(ph.has_trust_relationship(&self.identity) || node.this_node_is_root() || node.is_peer_root(self)) {
|
||||
async fn handle_incoming_hello<PH: InnerProtocolInterface>(&self, si: &SI, ph: &PH, node: &Node<SI>, time_ticks: i64, message_id: MessageId, source_path: &Arc<Path<SI>>, hops: u8, payload: &PacketBuffer) -> bool {
|
||||
if !(ph.should_communicate_with(&self.identity) || node.this_node_is_root() || node.is_peer_root(self)) {
|
||||
debug_event!(si, "[vl1] dropping HELLO from {} due to lack of trust relationship", self.identity.address.to_string());
|
||||
return true; // packet wasn't invalid, just ignored
|
||||
}
|
||||
|
@ -629,9 +599,8 @@ impl<SI: SystemInterface> Peer<SI> {
|
|||
{
|
||||
let mut remote_node_info = self.remote_node_info.write();
|
||||
remote_node_info.remote_protocol_version = hello_fixed_headers.version_proto;
|
||||
remote_node_info.remote_version = (hello_fixed_headers.version_major as u64).wrapping_shl(48)
|
||||
| (hello_fixed_headers.version_minor as u64).wrapping_shl(32)
|
||||
| (u16::from_be_bytes(hello_fixed_headers.version_revision) as u64).wrapping_shl(16);
|
||||
remote_node_info.remote_version =
|
||||
(hello_fixed_headers.version_major as u64).wrapping_shl(48) | (hello_fixed_headers.version_minor as u64).wrapping_shl(32) | (u16::from_be_bytes(hello_fixed_headers.version_revision) as u64).wrapping_shl(16);
|
||||
}
|
||||
|
||||
let mut packet = PacketBuffer::new();
|
||||
|
@ -648,7 +617,14 @@ impl<SI: SystemInterface> Peer<SI> {
|
|||
f.1.version_revision = VERSION_REVISION.to_be_bytes();
|
||||
}
|
||||
if hello_fixed_headers.version_proto >= 20 {
|
||||
let session_metadata = self.create_session_metadata(node, if hops == 0 { Some(&source_path.endpoint) } else { None });
|
||||
let session_metadata = self.create_session_metadata(
|
||||
node,
|
||||
if hops == 0 {
|
||||
Some(&source_path.endpoint)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
);
|
||||
assert!(session_metadata.len() <= 0xffff); // sanity check, should be impossible
|
||||
assert!(packet.append_u16(session_metadata.len() as u16).is_ok());
|
||||
assert!(packet.append_bytes(session_metadata.as_slice()).is_ok());
|
||||
|
@ -676,17 +652,7 @@ impl<SI: SystemInterface> Peer<SI> {
|
|||
return false;
|
||||
}
|
||||
|
||||
async fn handle_incoming_ok<PH: InnerProtocolInterface>(
|
||||
&self,
|
||||
si: &SI,
|
||||
ph: &PH,
|
||||
node: &Node<SI>,
|
||||
time_ticks: i64,
|
||||
source_path: &Arc<Path<SI>>,
|
||||
hops: u8,
|
||||
path_is_known: bool,
|
||||
payload: &PacketBuffer,
|
||||
) -> bool {
|
||||
async fn handle_incoming_ok<PH: InnerProtocolInterface>(&self, si: &SI, ph: &PH, node: &Node<SI>, time_ticks: i64, source_path: &Arc<Path<SI>>, hops: u8, path_is_known: bool, payload: &PacketBuffer) -> bool {
|
||||
let mut cursor = 0;
|
||||
if let Ok(ok_header) = payload.read_struct::<message_component_structs::OkHeader>(&mut cursor) {
|
||||
let in_re_message_id: MessageId = u64::from_ne_bytes(ok_header.in_re_message_id);
|
||||
|
@ -709,12 +675,7 @@ impl<SI: SystemInterface> Peer<SI> {
|
|||
let reported_endpoint2 = reported_endpoint.clone();
|
||||
if remote_node_info.reported_local_endpoints.insert(reported_endpoint, time_ticks).is_none() {
|
||||
#[cfg(debug_assertions)]
|
||||
debug_event!(
|
||||
si,
|
||||
"[vl1] {} reported new remote perspective, local endpoint: {}",
|
||||
self.identity.address.to_string(),
|
||||
reported_endpoint2.to_string()
|
||||
);
|
||||
debug_event!(si, "[vl1] {} reported new remote perspective, local endpoint: {}", self.identity.address.to_string(), reported_endpoint2.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -797,7 +758,7 @@ impl<SI: SystemInterface> Peer<SI> {
|
|||
}
|
||||
|
||||
async fn handle_incoming_whois<PH: InnerProtocolInterface>(&self, si: &SI, ph: &PH, node: &Node<SI>, time_ticks: i64, message_id: MessageId, payload: &PacketBuffer) -> bool {
|
||||
if node.this_node_is_root() || ph.has_trust_relationship(&self.identity) {
|
||||
if node.this_node_is_root() || ph.should_communicate_with(&self.identity) {
|
||||
let mut packet = PacketBuffer::new();
|
||||
packet.set_size(packet_constants::HEADER_SIZE);
|
||||
{
|
||||
|
@ -832,7 +793,7 @@ impl<SI: SystemInterface> Peer<SI> {
|
|||
}
|
||||
|
||||
async fn handle_incoming_echo<PH: InnerProtocolInterface>(&self, si: &SI, ph: &PH, node: &Node<SI>, time_ticks: i64, message_id: MessageId, payload: &PacketBuffer) -> bool {
|
||||
if ph.has_trust_relationship(&self.identity) || node.is_peer_root(self) {
|
||||
if ph.should_communicate_with(&self.identity) || node.is_peer_root(self) {
|
||||
let mut packet = PacketBuffer::new();
|
||||
packet.set_size(packet_constants::HEADER_SIZE);
|
||||
{
|
||||
|
|
|
@ -39,10 +39,19 @@ impl NetworkId {
|
|||
pub fn to_bytes(&self) -> [u8; 8] {
|
||||
self.0.get().to_be_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NetworkId> for u64 {
|
||||
#[inline(always)]
|
||||
pub fn to_u64(&self) -> u64 {
|
||||
self.0.get()
|
||||
fn from(v: NetworkId) -> Self {
|
||||
v.0.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&NetworkId> for u64 {
|
||||
#[inline(always)]
|
||||
fn from(v: &NetworkId) -> Self {
|
||||
v.0.get()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,16 +18,7 @@ impl InnerProtocolInterface for Switch {
|
|||
}
|
||||
|
||||
#[allow(unused)]
|
||||
async fn handle_error<SI: SystemInterface>(
|
||||
&self,
|
||||
peer: &Peer<SI>,
|
||||
source_path: &Path<SI>,
|
||||
in_re_verb: u8,
|
||||
in_re_message_id: u64,
|
||||
error_code: u8,
|
||||
payload: &PacketBuffer,
|
||||
cursor: &mut usize,
|
||||
) -> bool {
|
||||
async fn handle_error<SI: SystemInterface>(&self, peer: &Peer<SI>, source_path: &Path<SI>, in_re_verb: u8, in_re_message_id: u64, error_code: u8, payload: &PacketBuffer, cursor: &mut usize) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
|
@ -37,7 +28,7 @@ impl InnerProtocolInterface for Switch {
|
|||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn has_trust_relationship(&self, id: &Identity) -> bool {
|
||||
fn should_communicate_with(&self, id: &Identity) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue