mirror of
https://github.com/ZeroTier/ZeroTierOne
synced 2025-08-22 22:33:58 -07:00
Add back in send() and a few other things to Noise_XK ZSSP.
This commit is contained in:
parent
5763988e5b
commit
4474275ce6
2 changed files with 150 additions and 132 deletions
|
@ -59,12 +59,12 @@ pub(crate) const AES_CTR_NONCE_SIZE: usize = 12;
|
|||
/// This is large since some ZeroTier nodes handle huge numbers of links, like roots and controllers.
|
||||
pub(crate) const SESSION_ID_SIZE: usize = 6;
|
||||
|
||||
/// Maximum difference between out-of-order incoming packet counters, and size of deduplication buffer.
|
||||
pub(crate) const COUNTER_WINDOW_MAX_OUT_OF_ORDER: usize = 16;
|
||||
|
||||
/// Timeout for a "note to self" that must be returned by a session initiator.
|
||||
pub(crate) const BOB_NOTE_TO_SELF_TIMEOUT_MS: i64 = 500;
|
||||
|
||||
/// Maximum difference between out-of-order incoming packet counters, and size of deduplication buffer.
|
||||
pub(crate) const COUNTER_WINDOW_MAX_OOO: usize = 16;
|
||||
|
||||
/// Maximum skip-ahead for counter.
|
||||
///
|
||||
/// This is huge (2^24) because its real purpose is to filter out bad packets where decryption of
|
||||
|
@ -72,7 +72,7 @@ pub(crate) const BOB_NOTE_TO_SELF_TIMEOUT_MS: i64 = 500;
|
|||
pub(crate) const COUNTER_WINDOW_MAX_SKIP_AHEAD: u64 = 16777216;
|
||||
|
||||
// Key usage labels for sub-key derivation using NIST-style KBKDF (basically just HMAC KDF).
|
||||
pub(crate) const KBKDF_KEY_USAGE_LABEL_KEX_PAYLOAD_ENCRYPTION: u8 = b'X'; // intermediate keys used in key exchanges
|
||||
pub(crate) const KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION: u8 = b'X'; // intermediate keys used in key exchanges
|
||||
pub(crate) const KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION: u8 = b'x'; // intermediate keys used in key exchanges
|
||||
pub(crate) const KBKDF_KEY_USAGE_LABEL_HEADER_CHECK: u8 = b'H'; // AES-based header check code generation
|
||||
pub(crate) const KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB: u8 = b'A'; // AES-GCM in A->B direction
|
||||
|
@ -88,7 +88,7 @@ pub(crate) const HEADER_CHECK_AES_KEY_SIZE: usize = 16;
|
|||
/// the primary algorithm from NIST P-384 or the transport cipher from AES-GCM.
|
||||
pub(crate) const INITIAL_KEY: [u8; 64] = [
|
||||
// macOS command line to generate:
|
||||
// echo -n 'ZSSP_Noise_XKpsk2_NISTP384_?KYBER1024_AESGCM_SHA512' | shasum -a 512 | cut -d ' ' -f 1 | xxd -r -p | xxd -i
|
||||
// echo -n 'ZSSP_Noiselike_XKpsk2_NISTP384_?KYBER1024_AESGCM_SHA512' | shasum -a 512 | cut -d ' ' -f 1 | xxd -r -p | xxd -i
|
||||
0xc7, 0xde, 0xa3, 0xbe, 0x84, 0xe5, 0x91, 0x25, 0x30, 0x59, 0xc1, 0xc9, 0x5d, 0x22, 0xf5, 0x5a, 0xd0, 0x67, 0x9e, 0xf9, 0xf6, 0xbb,
|
||||
0xc0, 0x2a, 0x7f, 0xd0, 0x12, 0xb2, 0x0f, 0xed, 0x64, 0x7a, 0x86, 0x9f, 0x82, 0x19, 0xca, 0x84, 0xad, 0xf6, 0x61, 0xda, 0x59, 0xcc,
|
||||
0x40, 0xcf, 0x57, 0x68, 0x3e, 0xe4, 0xd6, 0xe7, 0xd1, 0xad, 0xe9, 0x56, 0x50, 0xf2, 0x38, 0x22, 0x88, 0xa3, 0x5c, 0x7f,
|
||||
|
|
204
zssp/src/zssp.rs
204
zssp/src/zssp.rs
|
@ -1,7 +1,7 @@
|
|||
// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md.
|
||||
|
||||
// ZSSP: ZeroTier Secure Session Protocol
|
||||
// FIPS compliant Noise_IK with Jedi powers and built-in attack-resistant large payload (fragmentation) support.
|
||||
// FIPS compliant Noise_XK with Jedi powers and built-in attack-resistant large payload (fragmentation) support.
|
||||
|
||||
use std::mem::size_of;
|
||||
use std::num::NonZeroU64;
|
||||
|
@ -11,7 +11,7 @@ use std::sync::{Mutex, RwLock};
|
|||
use pqc_kyber::KYBER_SECRETKEYBYTES;
|
||||
|
||||
use zerotier_crypto::aes::{Aes, AesCtr, AesGcm};
|
||||
use zerotier_crypto::hash::{hmac_sha512, HMACSHA384, HMAC_SHA384_SIZE, SHA384, SHA384_HASH_SIZE};
|
||||
use zerotier_crypto::hash::{hmac_sha512, HMACSHA384, HMAC_SHA384_SIZE, SHA384};
|
||||
use zerotier_crypto::p384::{P384KeyPair, P384PublicKey};
|
||||
use zerotier_crypto::secret::Secret;
|
||||
use zerotier_crypto::{random, secure_eq};
|
||||
|
@ -55,7 +55,9 @@ pub struct ReceiveContext<H: ApplicationLayer> {
|
|||
initial_offer_defrag: Mutex<RingBufferMap<u64, GatherArray<H::IncomingPacketBuffer, KEY_EXCHANGE_MAX_FRAGMENTS>, 1024, 128>>,
|
||||
}
|
||||
|
||||
/// A FIPS compliant variant of Noise_IK with hybrid Kyber1024 PQ data forward secrecy.
|
||||
/// ZeroTier Secure Session Protocol (ZSSP) Session
|
||||
///
|
||||
/// A FIPS/NIST compliant variant of Noise_XK with hybrid Kyber1024 PQ data forward secrecy.
|
||||
pub struct Session<Application: ApplicationLayer> {
|
||||
/// This side's locally unique session ID
|
||||
pub id: SessionId,
|
||||
|
@ -63,9 +65,10 @@ pub struct Session<Application: ApplicationLayer> {
|
|||
/// An arbitrary application defined object associated with each session
|
||||
pub application_data: Application::Data,
|
||||
|
||||
psk: Secret<64>, // External PSK provided by application
|
||||
psk: Secret<64>, // External PSK provided by application (all zero if none)
|
||||
remote_session_id: SessionId, // Other side's session ID
|
||||
send_counter: AtomicU64, // Outgoing packet counter and nonce state
|
||||
receive_window: [AtomicU64; COUNTER_WINDOW_MAX_OUT_OF_ORDER], // Receive window for anti-replay and deduplication
|
||||
receive_window: [AtomicU64; COUNTER_WINDOW_MAX_OOO], // Receive window for anti-replay and deduplication
|
||||
header_check_cipher: Aes, // Cipher used to protect header (not Noise related)
|
||||
offer: Mutex<EphemeralOffer>, // Most recently sent ephemeral offer
|
||||
state: RwLock<State>, // Miscellaneous mutable state
|
||||
|
@ -75,7 +78,8 @@ pub struct Session<Application: ApplicationLayer> {
|
|||
|
||||
enum EphemeralOffer {
|
||||
None,
|
||||
AliceNoiseXKInit(Secret<64>, P384KeyPair, [u8; KYBER_SECRETKEYBYTES]), // noise_es, alice_noise_e_secret, alice_hk_secret
|
||||
NoiseXKInit(Secret<64>, P384KeyPair, Secret<KYBER_SECRETKEYBYTES>), // noise_es, alice_noise_e_secret, alice_hk_secret
|
||||
Rekey(P384KeyPair),
|
||||
}
|
||||
|
||||
struct State {
|
||||
|
@ -89,9 +93,11 @@ struct SessionKey {
|
|||
receive_cipher_pool: Mutex<Vec<Box<AesGcm>>>, // Pool of reusable sending ciphers
|
||||
send_cipher_pool: Mutex<Vec<Box<AesGcm>>>, // Pool of reusable receiving ciphers
|
||||
rekey_at_time: i64, // Rekey at or after this time (ticks)
|
||||
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
|
||||
role_is_bob: bool, // Was this side "Bob" in this exchange?
|
||||
}
|
||||
|
||||
impl<Application: ApplicationLayer> Session<Application> {
|
||||
|
@ -173,7 +179,6 @@ impl<Application: ApplicationLayer> Session<Application> {
|
|||
/// * `send` - Function to call to send physical packet(s)
|
||||
/// * `mtu_sized_buffer` - A writable work buffer whose size also specifies the physical MTU
|
||||
/// * `data` - Data to send
|
||||
/*
|
||||
#[inline]
|
||||
pub fn send<SendFunction: FnMut(&mut [u8])>(
|
||||
&self,
|
||||
|
@ -183,15 +188,13 @@ impl<Application: ApplicationLayer> Session<Application> {
|
|||
) -> Result<(), Error> {
|
||||
debug_assert!(mtu_sized_buffer.len() >= MIN_TRANSPORT_MTU);
|
||||
let state = self.state.read().unwrap();
|
||||
if let Some(remote_session_id) = state.remote_session_id {
|
||||
if let Some(session_key) = state.session_keys[state.cur_session_key_idx].as_ref() {
|
||||
if let Some(session_key) = state.keys[state.current_key].as_ref() {
|
||||
let counter = self.send_counter.fetch_add(1, Ordering::SeqCst);
|
||||
|
||||
let mut c = session_key.get_send_cipher(counter)?;
|
||||
c.reset_init_gcm(&create_message_nonce(PACKET_TYPE_DATA, counter));
|
||||
|
||||
let fragment_count =
|
||||
(((data.len() + AES_GCM_TAG_SIZE) as f32) / (mtu_sized_buffer.len() - HEADER_SIZE) as f32).ceil() as usize;
|
||||
let fragment_count = (((data.len() + AES_GCM_TAG_SIZE) as f32) / (mtu_sized_buffer.len() - HEADER_SIZE) as f32).ceil() as usize;
|
||||
let fragment_max_chunk_size = mtu_sized_buffer.len() - HEADER_SIZE;
|
||||
let last_fragment_no = fragment_count - 1;
|
||||
for fragment_no in 0..fragment_count {
|
||||
|
@ -202,8 +205,8 @@ impl<Application: ApplicationLayer> Session<Application> {
|
|||
fragment_count,
|
||||
fragment_no,
|
||||
PACKET_TYPE_DATA,
|
||||
u64::from(remote_session_id),
|
||||
session_key.ratchet_count,
|
||||
u64::from(self.remote_session_id),
|
||||
state.current_key,
|
||||
counter,
|
||||
)?;
|
||||
c.crypt(&data[..chunk_size], &mut mtu_sized_buffer[HEADER_SIZE..fragment_size]);
|
||||
|
@ -226,20 +229,17 @@ impl<Application: ApplicationLayer> Session<Application> {
|
|||
} else {
|
||||
unlikely_branch();
|
||||
}
|
||||
} else {
|
||||
unlikely_branch();
|
||||
}
|
||||
return Err(Error::SessionNotEstablished);
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
/// Check whether this session is established.
|
||||
pub fn established(&self) -> bool {
|
||||
let state = self.state.read().unwrap();
|
||||
state.remote_session_id.is_some() && state.session_keys[state.cur_session_key_idx].is_some()
|
||||
state.keys[state.current_key].is_some()
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
/// Get the shared key fingerprint, ratchet count, and whether Kyber was used, or None if not yet established.
|
||||
pub fn status(&self) -> Option<([u8; 16], u64, Role, bool)> {
|
||||
let state = self.state.read().unwrap();
|
||||
|
@ -317,7 +317,7 @@ impl<Application: ApplicationLayer> Session<Application> {
|
|||
/// Check the receive window without mutating state.
|
||||
#[inline(always)]
|
||||
fn check_receive_window(&self, counter: u64) -> bool {
|
||||
let c = self.receive_window[(counter as usize) % COUNTER_WINDOW_MAX_OUT_OF_ORDER].load(Ordering::Acquire);
|
||||
let c = self.receive_window[(counter as usize) % COUNTER_WINDOW_MAX_OOO].load(Ordering::Acquire);
|
||||
c < counter && counter.wrapping_sub(c) < COUNTER_WINDOW_MAX_SKIP_AHEAD
|
||||
}
|
||||
|
||||
|
@ -325,7 +325,7 @@ impl<Application: ApplicationLayer> Session<Application> {
|
|||
/// This should only be called after the packet is authenticated.
|
||||
#[inline(always)]
|
||||
fn update_receive_window(&self, counter: u64) -> bool {
|
||||
let c = self.receive_window[(counter as usize) % COUNTER_WINDOW_MAX_OUT_OF_ORDER].fetch_max(counter, Ordering::AcqRel);
|
||||
let c = 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
|
||||
}
|
||||
}
|
||||
|
@ -493,10 +493,9 @@ impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
|||
|
||||
let message_nonce = create_message_nonce(packet_type, counter);
|
||||
if packet_type == PACKET_TYPE_DATA {
|
||||
/*
|
||||
if let Some(session) = session {
|
||||
let state = session.state.read().unwrap();
|
||||
if let Some(session_key) = state.session_keys[key_index].as_ref() {
|
||||
if let Some(session_key) = state.keys[key_index].as_ref() {
|
||||
let mut c = session_key.get_receive_cipher();
|
||||
c.reset_init_gcm(&message_nonce);
|
||||
|
||||
|
@ -546,18 +545,18 @@ impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
|||
// and if the current active key is older switch it to point to this one.
|
||||
if !session_key.confirmed {
|
||||
unlikely_branch();
|
||||
let this_ratchet_count = session_key.ratchet_count;
|
||||
let key_created_at_counter = session_key.created_at_counter;
|
||||
drop(state);
|
||||
let mut state = session.state.write().unwrap();
|
||||
|
||||
state.session_keys[key_index].as_mut().unwrap().confirmed = true;
|
||||
if state.cur_session_key_idx != key_index {
|
||||
if let Some(other_session_key) = state.session_keys[state.cur_session_key_idx].as_ref() {
|
||||
if other_session_key.ratchet_count < this_ratchet_count {
|
||||
state.cur_session_key_idx = key_index;
|
||||
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;
|
||||
}
|
||||
} else {
|
||||
state.cur_session_key_idx = key_index;
|
||||
state.current_key = key_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -574,8 +573,6 @@ impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
|||
unlikely_branch();
|
||||
return Err(Error::SessionNotEstablished);
|
||||
}
|
||||
*/
|
||||
todo!()
|
||||
} else {
|
||||
unlikely_branch();
|
||||
|
||||
|
@ -596,16 +593,21 @@ impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
|||
|
||||
/*
|
||||
* This is the first message Bob receives from Alice, the initiator. It contains
|
||||
* Alice's ephemeral keys but not her identity. Bob responds with his ephemeral
|
||||
* keys. An opaque sealed object called Bob's "note to self" is also sent. Alice
|
||||
* must return it with her final key exchange message. It contains Bob's state
|
||||
* for the exchange to this point (encrypted and authenticated) so that Bob does
|
||||
* not need to allocate any memory or mutate local state until Alice's identity
|
||||
* is known and confirmed.
|
||||
* Alice's ephemeral keys but not her identity. Alice will not reveal her identity
|
||||
* until forward secrecy is established and she's authenticated Bob.
|
||||
*
|
||||
* Bob authenticates the message and confirms that Alice indeed knows Bob's
|
||||
* identity, then responds with his ephemeral keys.
|
||||
*
|
||||
* Bob also sends an opaque sealed object called Bob's "note to self." It contains
|
||||
* Bob's state for the connection as of this first exchange, allowing Bob to be
|
||||
* stateless until he knows and has confirmed Alice's identity. It's encrypted,
|
||||
* authenticated, subject to a short TTL, and contains only information relevant
|
||||
* to the current exchange.
|
||||
*/
|
||||
|
||||
// There shouldn't be a session yet on Bob's end.
|
||||
if session.is_some() {
|
||||
if session.is_some() || counter != 1 {
|
||||
return Ok(ReceiveResult::Ignored);
|
||||
}
|
||||
|
||||
|
@ -614,7 +616,7 @@ impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
|||
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)?;
|
||||
|
||||
// Authenticate packet and prove that Alice knows our identity.
|
||||
// Authenticate packet and prove that Alice knows our static public key.
|
||||
if !secure_eq(
|
||||
&pkt.hmac_es,
|
||||
&hmac_sha384_2(
|
||||
|
@ -626,19 +628,17 @@ impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
|||
return Err(Error::FailedAuthentication);
|
||||
}
|
||||
|
||||
// Decrypt encrypted part of payload.
|
||||
let mut ctr = AesCtr::new(
|
||||
&kbkdf512(noise_es.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_PAYLOAD_ENCRYPTION).as_bytes()[..AES_KEY_SIZE],
|
||||
);
|
||||
// Decrypt encrypted part of payload (already authenticated above).
|
||||
let mut ctr =
|
||||
AesCtr::new(&kbkdf512(noise_es.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION).as_bytes()[..AES_KEY_SIZE]);
|
||||
ctr.reset_set_iv(&SHA384::hash(&pkt.alice_noise_e)[..AES_CTR_NONCE_SIZE]);
|
||||
ctr.crypt_in_place(&mut pkt_assembled[NoiseXKAliceEphemeralOffer::ENC_START..NoiseXKAliceEphemeralOffer::AUTH_START]);
|
||||
let pkt: &NoiseXKAliceEphemeralOffer = byte_array_as_proto_buffer(pkt_assembled)?;
|
||||
|
||||
let alice_session_id = SessionId::new_from_bytes(&pkt.alice_session_id).ok_or(Error::InvalidPacket)?;
|
||||
|
||||
// Create Bob's ephemeral keys and derive noise_es_ee.
|
||||
let (bob_hk_ciphertext, hk) = pqc_kyber::encapsulate(&pkt.alice_hk_public, &mut random::SecureRandom::default())
|
||||
.map_err(|_| Error::FailedAuthentication)?;
|
||||
// Create Bob's ephemeral keys and derive noise_es_ee by agreeing with Alice's. Also create
|
||||
// a Kyber ciphertext to send back to Alice and a shared secret Alice will decrypt.
|
||||
let bob_noise_e_secret = P384KeyPair::generate();
|
||||
let noise_es_ee = Secret(hmac_sha512(
|
||||
noise_es.as_bytes(),
|
||||
|
@ -647,6 +647,8 @@ impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
|||
.ok_or(Error::FailedAuthentication)?
|
||||
.as_bytes(),
|
||||
));
|
||||
let (bob_hk_ciphertext, hk) = pqc_kyber::encapsulate(&pkt.alice_hk_public, &mut random::SecureRandom::default())
|
||||
.map_err(|_| Error::FailedAuthentication)?;
|
||||
|
||||
// Create Bob's ephemeral counter-offer reply.
|
||||
let mut reply_buffer = [0u8; NOISE_MAX_HANDSHAKE_PACKET_SIZE];
|
||||
|
@ -668,10 +670,9 @@ impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
|||
gcm.crypt_in_place(&mut reply.bob_note_to_self[BobNoteToSelf::ENC_START..BobNoteToSelf::AUTH_START]);
|
||||
reply.bob_note_to_self[BobNoteToSelf::AUTH_START..].copy_from_slice(&gcm.finish_encrypt());
|
||||
|
||||
// Encrypt encrypted part of reply packet.
|
||||
let mut ctr = AesCtr::new(
|
||||
&kbkdf512(noise_es_ee.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_PAYLOAD_ENCRYPTION).as_bytes()[..AES_KEY_SIZE],
|
||||
);
|
||||
// Encrypt reply between bob_noise_e and HMAC.
|
||||
let mut ctr =
|
||||
AesCtr::new(&kbkdf512(noise_es_ee.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION).as_bytes()[..AES_KEY_SIZE]);
|
||||
ctr.reset_set_iv(&SHA384::hash(bob_noise_e_secret.public_key_bytes())[..AES_CTR_NONCE_SIZE]);
|
||||
ctr.crypt_in_place(
|
||||
&mut reply_buffer[NoiseXKBobEphemeralCounterOffer::ENC_START..NoiseXKBobEphemeralCounterOffer::AUTH_START],
|
||||
|
@ -713,10 +714,13 @@ impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
|||
* the negotiation.
|
||||
*/
|
||||
|
||||
if counter != 1 {
|
||||
return Ok(ReceiveResult::Ignored);
|
||||
}
|
||||
if let Some(session) = session {
|
||||
let mut offer = session.offer.lock().unwrap();
|
||||
match &*offer {
|
||||
EphemeralOffer::AliceNoiseXKInit(noise_es, alice_e_secret, alice_hk_secret) => {
|
||||
EphemeralOffer::NoiseXKInit(noise_es, alice_e_secret, alice_hk_secret) => {
|
||||
let pkt: &NoiseXKBobEphemeralCounterOffer = byte_array_as_proto_buffer(pkt_assembled)?;
|
||||
|
||||
// Derive noise_es_ee from Bob's ephemeral public key.
|
||||
|
@ -726,7 +730,7 @@ impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
|||
alice_e_secret.agree(&bob_noise_e).ok_or(Error::FailedAuthentication)?.as_bytes(),
|
||||
));
|
||||
|
||||
let noise_es_ee_kex_key = kbkdf512(noise_es_ee.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_PAYLOAD_ENCRYPTION);
|
||||
let noise_es_ee_kex_enc_key = kbkdf512(noise_es_ee.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION);
|
||||
let noise_es_ee_kex_hmac_key = kbkdf512(noise_es_ee.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION);
|
||||
|
||||
// Authenticate Bob's reply and the validity of bob_noise_e.
|
||||
|
@ -735,14 +739,14 @@ impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
|||
&hmac_sha384_2(
|
||||
&noise_es_ee_kex_hmac_key.as_bytes()[..HMAC_SHA384_SIZE],
|
||||
&message_nonce,
|
||||
&pkt_assembled[HEADER_SIZE..NoiseXKAliceEphemeralOffer::AUTH_START],
|
||||
&pkt_assembled[HEADER_SIZE..NoiseXKBobEphemeralCounterOffer::AUTH_START],
|
||||
),
|
||||
) {
|
||||
return Err(Error::FailedAuthentication);
|
||||
}
|
||||
|
||||
// Decrypt encrypted portion after authentication.
|
||||
let mut ctr = AesCtr::new(&noise_es_ee_kex_key.as_bytes()[..AES_KEY_SIZE]);
|
||||
let mut ctr = AesCtr::new(&noise_es_ee_kex_enc_key.as_bytes()[..AES_KEY_SIZE]);
|
||||
ctr.reset_set_iv(&SHA384::hash(&pkt.bob_noise_e)[..AES_CTR_NONCE_SIZE]);
|
||||
ctr.crypt_in_place(
|
||||
&mut pkt_assembled
|
||||
|
@ -750,9 +754,10 @@ impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
|||
);
|
||||
let pkt: &NoiseXKBobEphemeralCounterOffer = byte_array_as_proto_buffer(pkt_assembled)?;
|
||||
|
||||
// Noise_XKpsk3 specifies mixing a PSK last. The kyber1024 hybrid key is mixed into
|
||||
// the static PSK and then this is treated as the PSK as far as Noise is concerned.
|
||||
let hk = pqc_kyber::decapsulate(&pkt.bob_hk_ciphertext, alice_hk_secret)
|
||||
// Complete Noise_XKpsk3 by mixing in noise_se followed by the PSK. The PSK as far as
|
||||
// the Noise pattern is concerned is the result of mixing the externally supplied PSK
|
||||
// with the Kyber1024 shared secret (hk).
|
||||
let hk = pqc_kyber::decapsulate(&pkt.bob_hk_ciphertext, alice_hk_secret.as_bytes())
|
||||
.map_err(|_| Error::FailedAuthentication)?;
|
||||
let noise_es_ee_se_hk_psk = Secret(hmac_sha512(
|
||||
&hmac_sha512(
|
||||
|
@ -785,8 +790,10 @@ impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
|||
reply_buffer_append(alice_s_public_blob);
|
||||
reply_buffer_append(&[0u8, 0u8]); // no meta-data
|
||||
|
||||
// Encrypt Alice's static identity and other inner payload items.
|
||||
let mut ctr = AesCtr::new(&noise_es_ee_kex_key.as_bytes()[..AES_KEY_SIZE]);
|
||||
// Encrypt Alice's static identity and other inner payload items. The IV here
|
||||
// is a hash of 'hk' making it actually a secret and "borrowing" a little PQ
|
||||
// forward secrecy for Alice's identity.
|
||||
let mut ctr = AesCtr::new(&noise_es_ee_kex_enc_key.as_bytes()[..AES_KEY_SIZE]);
|
||||
ctr.reset_set_iv(&SHA384::hash(&hk)[..AES_CTR_NONCE_SIZE]);
|
||||
ctr.crypt_in_place(&mut reply_buffer[NOISE_XK_ALICE_STATIC_ACK_ENCRYPTED_SECTION_START..reply_len]);
|
||||
|
||||
|
@ -801,7 +808,8 @@ impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
|||
reply_len += HMAC_SHA384_SIZE;
|
||||
|
||||
// Then attach the final HMAC permitting Bob to verify the authenticity of the whole
|
||||
// key exchange.
|
||||
// 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.
|
||||
let hmac_es_ee_se_hk_psk = hmac_sha384_2(
|
||||
&kbkdf512(noise_es_ee_se_hk_psk.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION).as_bytes()
|
||||
[..HMAC_SHA384_SIZE],
|
||||
|
@ -844,11 +852,9 @@ impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
|||
* that Alice must return.
|
||||
*/
|
||||
|
||||
// There shouldn't be a session yet on Bob's end.
|
||||
if session.is_some() {
|
||||
return Ok(ReceiveResult::Ignored);
|
||||
}
|
||||
|
||||
if pkt_assembled.len() < NOISE_XK_ALICE_STATIC_ACK_MIN_SIZE {
|
||||
return Err(Error::InvalidPacket);
|
||||
}
|
||||
|
@ -876,7 +882,7 @@ impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
|||
return Err(Error::FailedAuthentication);
|
||||
}
|
||||
|
||||
// Restore state from when Alice first tried to contact us.
|
||||
// Restore state from note to self, returning to where we were after Alice's first contact.
|
||||
let alice_session_id = SessionId::new_from_bytes(&bob_note_to_self.alice_session_id).ok_or(Error::InvalidPacket)?;
|
||||
let bob_noise_e_secret = P384KeyPair::from_bytes(&bob_note_to_self.bob_noise_e, &bob_note_to_self.bob_noise_e_secret)
|
||||
.ok_or(Error::InvalidPacket)?;
|
||||
|
@ -884,9 +890,8 @@ impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
|||
let noise_es_ee = Secret(bob_note_to_self.noise_es_ee);
|
||||
drop(bob_note_to_self_buffer);
|
||||
|
||||
let pkt_assembled_enc_end = pkt_assembled.len() - (HMAC_SHA384_SIZE * 2);
|
||||
|
||||
// Authenticate packet with noise_es_ee (first HMAC) before decrypting and parsing static info.
|
||||
let pkt_assembled_enc_end = pkt_assembled.len() - (HMAC_SHA384_SIZE * 2);
|
||||
if !secure_eq(
|
||||
&pkt_assembled[pkt_assembled_enc_end..pkt_assembled.len() - HMAC_SHA384_SIZE],
|
||||
&hmac_sha384_2(
|
||||
|
@ -898,13 +903,13 @@ impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
|||
return Err(Error::FailedAuthentication);
|
||||
}
|
||||
|
||||
// Save a copy of the encrypted unmodified packet for final HMAC.
|
||||
let mut pkt_saved_for_final_hmac = [0u8; NOISE_MAX_HANDSHAKE_PACKET_SIZE];
|
||||
pkt_saved_for_final_hmac[..pkt_assembled.len()].copy_from_slice(pkt_assembled);
|
||||
|
||||
// Decrypt Alice's static identity and decode.
|
||||
let mut ctr = AesCtr::new(
|
||||
&kbkdf512(noise_es_ee.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_PAYLOAD_ENCRYPTION).as_bytes()[..AES_KEY_SIZE],
|
||||
);
|
||||
let mut ctr =
|
||||
AesCtr::new(&kbkdf512(noise_es_ee.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION).as_bytes()[..AES_KEY_SIZE]);
|
||||
ctr.reset_set_iv(&SHA384::hash(hk.as_bytes())[..AES_CTR_NONCE_SIZE]);
|
||||
ctr.crypt_in_place(&mut pkt_assembled[NOISE_XK_ALICE_STATIC_ACK_ENCRYPTED_SECTION_START..pkt_assembled_enc_end]);
|
||||
|
||||
|
@ -932,6 +937,7 @@ impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
|||
if let Some((bob_session_id, psk, app_data)) =
|
||||
app.accept_new_session(self, remote_address, alice_static_public_blob, meta_data)
|
||||
{
|
||||
// Create final Noise_XKpsk3 shared secret on this side.
|
||||
let noise_es_ee_se_hk_psk = Secret(hmac_sha512(
|
||||
&hmac_sha512(
|
||||
noise_es_ee.as_bytes(),
|
||||
|
@ -959,7 +965,24 @@ impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
|||
return Err(Error::FailedAuthentication);
|
||||
}
|
||||
|
||||
todo!()
|
||||
return Ok(ReceiveResult::OkNewSession(Session {
|
||||
id: bob_session_id,
|
||||
application_data: app_data,
|
||||
psk,
|
||||
remote_session_id: alice_session_id,
|
||||
send_counter: AtomicU64::new(2), // 1 was already used in our first reply
|
||||
receive_window: std::array::from_fn(|_| AtomicU64::new(0)),
|
||||
header_check_cipher: Aes::new(
|
||||
&kbkdf512(noise_es_ee.as_bytes(), KBKDF_KEY_USAGE_LABEL_HEADER_CHECK).as_bytes()
|
||||
[..AES_HEADER_CHECK_KEY_SIZE],
|
||||
),
|
||||
offer: Mutex::new(EphemeralOffer::None),
|
||||
state: RwLock::new(State {
|
||||
keys: [Some(SessionKey::new(noise_es_ee_se_hk_psk, current_time, 2, true, true)), None],
|
||||
current_key: 0,
|
||||
}),
|
||||
defrag: Mutex::new(RingBufferMap::new(random::xorshift64_random() as u32)),
|
||||
}));
|
||||
} else {
|
||||
return Err(Error::NewSessionRejected);
|
||||
}
|
||||
|
@ -983,7 +1006,7 @@ fn set_packet_header(
|
|||
fragment_no: usize,
|
||||
packet_type: u8,
|
||||
recipient_session_id: u64,
|
||||
ratchet_count: u64,
|
||||
key_index: usize,
|
||||
counter: u64,
|
||||
) -> Result<(), Error> {
|
||||
debug_assert!(packet.len() >= MIN_PACKET_SIZE);
|
||||
|
@ -993,14 +1016,14 @@ fn set_packet_header(
|
|||
if fragment_count <= MAX_FRAGMENTS {
|
||||
// [0-47] recipient session ID
|
||||
// -- start of header check cipher single block encrypt --
|
||||
// [48-48] key index (least significant bit of ratchet count)
|
||||
// [48-48] key index (least significant bit)
|
||||
// [49-51] packet type (0-15)
|
||||
// [52-57] fragment count (1..64 - 1, so 0 means 1 fragment)
|
||||
// [58-63] fragment number (0..63)
|
||||
// [64-127] 64-bit counter
|
||||
memory::store_raw(
|
||||
(u64::from(recipient_session_id)
|
||||
| (ratchet_count & 1).wrapping_shl(48)
|
||||
| ((key_index & 1) as u64).wrapping_shl(48)
|
||||
| (packet_type as u64).wrapping_shl(49)
|
||||
| ((fragment_count - 1) as u64).wrapping_shl(52)
|
||||
| (fragment_no as u64).wrapping_shl(58))
|
||||
|
@ -1041,7 +1064,7 @@ fn send_with_fragmentation<SendFunction: FnMut(&mut [u8])>(
|
|||
mtu: usize,
|
||||
packet_type: u8,
|
||||
recipient_session_id: Option<SessionId>,
|
||||
ratchet_count: u64,
|
||||
key_index: usize,
|
||||
counter: u64,
|
||||
header_check_cipher: Option<&Aes>,
|
||||
) -> Result<(), Error> {
|
||||
|
@ -1058,7 +1081,7 @@ fn send_with_fragmentation<SendFunction: FnMut(&mut [u8])>(
|
|||
fragment_no,
|
||||
packet_type,
|
||||
recipient_session_id,
|
||||
ratchet_count,
|
||||
key_index,
|
||||
counter,
|
||||
)?;
|
||||
if let Some(hcc) = header_check_cipher {
|
||||
|
@ -1071,32 +1094,28 @@ fn send_with_fragmentation<SendFunction: FnMut(&mut [u8])>(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/*
|
||||
impl SessionKey {
|
||||
/// Create a new symmetric shared session key and set its key expiration times, etc.
|
||||
fn new(key: Secret<64>, role: Role, current_time: i64, current_counter: u64, ratchet_count: u64, confirmed: bool, jedi: bool) -> Self {
|
||||
fn new(key: Secret<64>, current_time: i64, current_counter: u64, confirmed: bool, role_is_bob: bool) -> Self {
|
||||
let a2b: Secret<AES_KEY_SIZE> = kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB).first_n_clone();
|
||||
let b2a: Secret<AES_KEY_SIZE> = kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE).first_n_clone();
|
||||
let (receive_key, send_key) = match role {
|
||||
Role::Alice => (b2a, a2b),
|
||||
Role::Bob => (a2b, b2a),
|
||||
let (receive_key, send_key) = if role_is_bob {
|
||||
(a2b, b2a)
|
||||
} else {
|
||||
(b2a, a2b)
|
||||
};
|
||||
Self {
|
||||
ratchet_count,
|
||||
rekey_at_time: current_time
|
||||
.checked_add(REKEY_AFTER_TIME_MS + ((random::xorshift64_random() as u32) % REKEY_AFTER_TIME_MS_MAX_JITTER) as i64)
|
||||
.unwrap(),
|
||||
rekey_at_counter: current_counter.checked_add(REKEY_AFTER_USES).unwrap(),
|
||||
expire_at_counter: current_counter.checked_add(EXPIRE_AFTER_USES).unwrap(),
|
||||
secret_fingerprint: public_fingerprint_of_secret(key.as_bytes())[..16].try_into().unwrap(),
|
||||
ratchet_key: kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_RATCHETING),
|
||||
receive_key,
|
||||
send_key,
|
||||
receive_cipher_pool: Mutex::new(Vec::with_capacity(2)),
|
||||
send_cipher_pool: Mutex::new(Vec::with_capacity(2)),
|
||||
role,
|
||||
rekey_at_time: current_time
|
||||
.checked_add(REKEY_AFTER_TIME_MS + ((random::xorshift64_random() as u32) % REKEY_AFTER_TIME_MS_MAX_JITTER) as i64)
|
||||
.unwrap(),
|
||||
created_at_counter: current_counter,
|
||||
rekey_at_counter: current_counter.checked_add(REKEY_AFTER_USES).unwrap(),
|
||||
expire_at_counter: current_counter.checked_add(EXPIRE_AFTER_USES).unwrap(),
|
||||
confirmed,
|
||||
jedi,
|
||||
role_is_bob,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1136,7 +1155,6 @@ impl SessionKey {
|
|||
self.receive_cipher_pool.lock().unwrap().push(c);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/// Shortcut to HMAC data split into two slices.
|
||||
fn hmac_sha384_2(key: &[u8], a: &[u8], b: &[u8]) -> [u8; 48] {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue