Just about ready to test... wow.

This commit is contained in:
Adam Ierymenko 2023-02-27 18:33:37 -05:00
commit 6367b8e10f
2 changed files with 74 additions and 82 deletions

View file

@ -12,7 +12,6 @@ use pqc_kyber::{KYBER_CIPHERTEXTBYTES, KYBER_PUBLICKEYBYTES};
use zerotier_crypto::hash::{HMAC_SHA384_SIZE, SHA384_HASH_SIZE};
use zerotier_crypto::p384::P384_PUBLIC_KEY_SIZE;
use crate::applicationlayer::ApplicationLayer;
use crate::error::Error;
use crate::sessionid::SessionId;
@ -156,27 +155,6 @@ impl BobRekeyAck {
pub const SIZE: usize = Self::AUTH_START + AES_GCM_TAG_SIZE;
}
/// Assemble a series of fragments into a buffer and return the length of the assembled packet in bytes.
pub(crate) fn assemble_fragments_into<A: ApplicationLayer>(fragments: &[A::IncomingPacketBuffer], d: &mut [u8]) -> Result<usize, Error> {
let mut l = 0;
for i in 0..fragments.len() {
let mut ff = fragments[i].as_ref();
if ff.len() <= MIN_PACKET_SIZE {
return Err(Error::InvalidPacket);
}
if i > 0 {
ff = &ff[HEADER_SIZE..];
}
let j = l + ff.len();
if j > d.len() {
return Err(Error::InvalidPacket);
}
d[l..j].copy_from_slice(ff);
l = j;
}
return Ok(l);
}
// Annotate only these structs as being compatible with packet_buffer_as_bytes(). These structs
// are packed flat buffers containing only byte or byte array fields, making them safe to treat
// this way even on architectures that require type size aligned access.
@ -187,6 +165,24 @@ impl ProtocolFlatBuffer for BobNoiseXKAck {}
impl ProtocolFlatBuffer for AliceRekeyInit {}
impl ProtocolFlatBuffer for BobRekeyAck {}
#[derive(Clone, Copy)]
#[repr(C, packed)]
struct MessageNonceCreateBuffer(u64, u32);
/// Create a 96-bit AES-GCM nonce.
///
/// The primary information that we want to be contained here is the counter and the
/// packet type. The former makes this unique and the latter's inclusion authenticates
/// it as effectively AAD. Other elements of the header are either not authenticated,
/// like fragmentation info, or their authentication is implied via key exchange like
/// the session ID.
///
/// This is also used as part of HMAC authentication for key exchange packets.
#[inline(always)]
pub(crate) fn create_message_nonce(packet_type: u8, counter: u64) -> [u8; AES_GCM_NONCE_SIZE] {
unsafe { std::mem::transmute(MessageNonceCreateBuffer(counter.to_le(), (packet_type as u32).to_le())) }
}
#[inline(always)]
pub(crate) fn byte_array_as_proto_buffer<B: ProtocolFlatBuffer>(b: &[u8]) -> Result<&B, Error> {
if b.len() >= size_of::<B>() {

View file

@ -40,11 +40,11 @@ pub struct Context<Application: ApplicationLayer> {
max_incomplete_session_queue_size: usize,
initial_offer_defrag:
Mutex<RingBufferMap<u64, GatherArray<Application::IncomingPacketBuffer, MAX_NOISE_HANDSHAKE_FRAGMENTS>, 256, 256>>,
sessions: RwLock<SessionMaps<Application>>,
sessions: RwLock<SessionsById<Application>>,
}
/// Lookup maps for sessions within a session context.
struct SessionMaps<Application: ApplicationLayer> {
struct SessionsById<Application: ApplicationLayer> {
// Active sessions, automatically closed if the application no longer holds their Arc<>.
active: HashMap<SessionId, Weak<Session<Application>>>,
@ -134,7 +134,6 @@ struct SessionKey {
created_at_counter: u64, // Counter at which session was created
rekey_at_counter: u64, // Rekey at or after this counter
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
bob: bool, // Was this side "Bob" in this exchange?
}
@ -144,7 +143,7 @@ impl<Application: ApplicationLayer> Context<Application> {
Self {
max_incomplete_session_queue_size,
initial_offer_defrag: Mutex::new(RingBufferMap::new(random::next_u32_secure())),
sessions: RwLock::new(SessionMaps {
sessions: RwLock::new(SessionsById {
active: HashMap::with_capacity(64),
incomplete: HashMap::with_capacity(64),
}),
@ -265,7 +264,7 @@ impl<Application: ApplicationLayer> Context<Application> {
let alice_noise_e = alice_noise_e_secret.public_key_bytes().clone();
let noise_es = alice_noise_e_secret.agree(&remote_s_public_p384).ok_or(Error::InvalidParameter)?;
let alice_hk_secret = pqc_kyber::keypair(&mut random::SecureRandom::default());
let header_protection_key: [u8; AES_HEADER_PROTECTION_KEY_SIZE] = random::get_bytes_secure();
let header_protection_key: Secret<AES_HEADER_PROTECTION_KEY_SIZE> = Secret(random::get_bytes_secure());
let (local_session_id, session) = {
let mut sessions = self.sessions.write().unwrap();
@ -284,7 +283,7 @@ impl<Application: ApplicationLayer> Context<Application> {
psk,
send_counter: AtomicU64::new(3), // 1 and 2 are reserved for init and final ack
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.as_bytes()),
state: RwLock::new(State {
remote_session_id: None,
keys: [None, None],
@ -319,7 +318,7 @@ impl<Application: ApplicationLayer> Context<Application> {
init.alice_noise_e = alice_noise_e;
init.alice_session_id = *local_session_id.as_bytes();
init.alice_hk_public = alice_hk_secret.public;
init.header_protection_key = header_protection_key;
init.header_protection_key = header_protection_key.0;
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..]);
@ -585,18 +584,15 @@ impl<Application: ApplicationLayer> Context<Application> {
if aead_authentication_ok {
if session.update_receive_window(incoming_counter) {
// If the packet authenticated, this confirms that the other side indeed
// 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.
if key.confirmed {
// Update the current key to point to this key if it's newer, since having received
// a packet encrypted with it proves that the other side has successfully derived it
// as well.
if state.current_key == key_index {
drop(state);
} else {
let key_created_at_counter = key.created_at_counter;
drop(state);
let mut state = session.state.write().unwrap();
state.keys[key_index].as_mut().unwrap().confirmed = true;
if state.current_key != key_index {
if let Some(other_session_key) = state.keys[state.current_key].as_ref() {
if other_session_key.created_at_counter < key_created_at_counter {
state.current_key = key_index;
@ -605,7 +601,6 @@ impl<Application: ApplicationLayer> Context<Application> {
state.current_key = key_index;
}
}
}
return Ok(ReceiveResult::OkData(session, &mut data_buf[..data_len]));
} else {
@ -941,13 +936,8 @@ impl<Application: ApplicationLayer> Context<Application> {
{
let mut state = session.state.write().unwrap();
let _ = state.remote_session_id.insert(bob_session_id);
let _ = state.keys[0].insert(SessionKey::new::<Application>(
noise_es_ee_se_hk_psk,
current_time,
2,
true,
false,
));
let _ =
state.keys[0].insert(SessionKey::new::<Application>(noise_es_ee_se_hk_psk, current_time, 2, true));
state.current_key = 0;
state.current_offer = Offer::None;
}
@ -1099,7 +1089,7 @@ impl<Application: ApplicationLayer> Context<Application> {
state: RwLock::new(State {
remote_session_id: Some(incomplete.alice_session_id),
keys: [
Some(SessionKey::new::<Application>(noise_es_ee_se_hk_psk, current_time, 2, true, true)),
Some(SessionKey::new::<Application>(noise_es_ee_se_hk_psk, current_time, 2, true)),
None,
],
current_key: 0,
@ -1144,8 +1134,10 @@ impl<Application: ApplicationLayer> Context<Application> {
let pkt: &AliceRekeyInit = byte_array_as_proto_buffer(&pkt_assembled).unwrap();
if let Some(alice_e) = P384PublicKey::from_bytes(&pkt.alice_e) {
let bob_e_secret = P384KeyPair::generate();
let ee = bob_e_secret.agree(&alice_e).ok_or(Error::FailedAuthentication)?;
let next_session_key = Secret(hmac_sha512(key.ratchet_key.as_bytes(), ee.as_bytes()));
let next_session_key = Secret(hmac_sha512(
key.ratchet_key.as_bytes(),
bob_e_secret.agree(&alice_e).ok_or(Error::FailedAuthentication)?.as_bytes(),
));
let mut reply_buf = [0u8; BobRekeyAck::SIZE];
let reply: &mut BobRekeyAck = byte_array_as_proto_buffer_mut(&mut reply_buf).unwrap();
@ -1168,7 +1160,6 @@ impl<Application: ApplicationLayer> Context<Application> {
current_time,
counter.get(),
false,
true,
));
return Ok(ReceiveResult::Ok);
@ -1206,19 +1197,22 @@ impl<Application: ApplicationLayer> Context<Application> {
if aead_authentication_ok {
let pkt: &BobRekeyAck = byte_array_as_proto_buffer(&pkt_assembled).unwrap();
if let Some(bob_e) = P384PublicKey::from_bytes(&pkt.bob_e) {
let ee = alice_e_secret.agree(&bob_e).ok_or(Error::FailedAuthentication)?;
let next_session_key = Secret(hmac_sha512(key.ratchet_key.as_bytes(), ee.as_bytes()));
let next_session_key = Secret(hmac_sha512(
key.ratchet_key.as_bytes(),
alice_e_secret.agree(&bob_e).ok_or(Error::FailedAuthentication)?.as_bytes(),
));
if secure_eq(&pkt.next_key_fingerprint, &SHA384::hash(next_session_key.as_bytes())) {
drop(state);
let next_key_index = key_index ^ 1;
let mut state = session.state.write().unwrap();
let _ = state.keys[key_index ^ 1].replace(SessionKey::new::<Application>(
let _ = state.keys[next_key_index].replace(SessionKey::new::<Application>(
next_session_key,
current_time,
session.send_counter.load(Ordering::Acquire),
true,
false,
));
state.current_key = next_key_index; // this is an ACK so it's confirmed
state.current_offer = Offer::None;
return Ok(ReceiveResult::Ok);
@ -1419,26 +1413,11 @@ fn parse_packet_header(incoming_packet: &[u8]) -> (usize, u8, u8, u8, u64) {
(key_index, packet_type, fragment_count, fragment_no, counter)
}
#[derive(Clone, Copy)]
#[repr(C, packed)]
struct MessageNonce(u64, u32);
/// Create a 96-bit AES-GCM nonce.
///
/// The primary information that we want to be contained here is the counter and the
/// packet type. The former makes this unique and the latter's inclusion authenticates
/// it as effectively AAD. Other elements of the header are either not authenticated,
/// like fragmentation info, or their authentication is implied via key exchange like
/// the session ID.
///
/// This is also used as part of HMAC authentication for key exchange packets.
#[inline(always)]
fn create_message_nonce(packet_type: u8, counter: u64) -> [u8; AES_GCM_NONCE_SIZE] {
memory::to_byte_array(MessageNonce(counter.to_le(), (packet_type as u32).to_le()))
}
/// Break a packet into fragments and send them all.
///
/// The contents of packet[] are mangled during this operation, so it should be discarded after.
/// This is only used for key exchange and control packets. For data packets this is done inline
/// for better performance with encryption and fragmentation happening at the same time.
fn send_with_fragmentation<SendFunction: FnMut(&mut [u8])>(
mut send: SendFunction,
packet: &mut [u8],
@ -1475,14 +1454,32 @@ fn send_with_fragmentation<SendFunction: FnMut(&mut [u8])>(
Ok(())
}
/// Assemble a series of fragments into a buffer and return the length of the assembled packet in bytes.
///
/// This is also only used for key exchange and control packets. For data packets decryption and assembly
/// happen in one pass for better performance.
fn assemble_fragments_into<A: ApplicationLayer>(fragments: &[A::IncomingPacketBuffer], d: &mut [u8]) -> Result<usize, Error> {
let mut l = 0;
for i in 0..fragments.len() {
let mut ff = fragments[i].as_ref();
if ff.len() <= MIN_PACKET_SIZE {
return Err(Error::InvalidPacket);
}
if i > 0 {
ff = &ff[HEADER_SIZE..];
}
let j = l + ff.len();
if j > d.len() {
return Err(Error::InvalidPacket);
}
d[l..j].copy_from_slice(ff);
l = j;
}
return Ok(l);
}
impl SessionKey {
fn new<Application: ApplicationLayer>(
key: Secret<BASE_KEY_SIZE>,
current_time: i64,
current_counter: u64,
confirmed: bool,
role_is_bob: bool,
) -> Self {
fn new<Application: ApplicationLayer>(key: Secret<BASE_KEY_SIZE>, current_time: i64, current_counter: u64, role_is_bob: bool) -> Self {
let a2b = kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB>(key.as_bytes());
let b2a = kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE>(key.as_bytes());
let (receive_key, send_key) = if role_is_bob {
@ -1505,7 +1502,6 @@ impl SessionKey {
created_at_counter: current_counter,
rekey_at_counter: current_counter.checked_add(Application::REKEY_AFTER_USES).unwrap(),
expire_at_counter: current_counter.checked_add(Application::EXPIRE_AFTER_USES).unwrap(),
confirmed,
bob: role_is_bob,
}
}