Make ZSSP use just GCM to simplify, and change final ACK to auth and encrypt public blob separately from meta-data.

This commit is contained in:
Adam Ierymenko 2023-03-07 13:03:48 -05:00
parent e66477c168
commit 757cc88abc
3 changed files with 165 additions and 207 deletions

View file

@ -80,7 +80,7 @@ fn alice_main(
match context.receive( match context.receive(
alice_app, alice_app,
|| true, || true,
|s_public, _| Some((P384PublicKey::from_bytes(s_public).unwrap(), Secret::default(), ())), |s_public| Some((P384PublicKey::from_bytes(s_public).unwrap(), Secret::default(), ())),
|_, b| { |_, b| {
let _ = alice_out.send(b.to_vec()); let _ = alice_out.send(b.to_vec());
}, },
@ -96,7 +96,7 @@ fn alice_main(
Ok(zssp::ReceiveResult::OkData(_, _)) => { Ok(zssp::ReceiveResult::OkData(_, _)) => {
//println!("[alice] received {}", data.len()); //println!("[alice] received {}", data.len());
} }
Ok(zssp::ReceiveResult::OkNewSession(s)) => { Ok(zssp::ReceiveResult::OkNewSession(s, _)) => {
println!("[alice] new session {}", s.id.to_string()); println!("[alice] new session {}", s.id.to_string());
} }
Ok(zssp::ReceiveResult::Rejected) => {} Ok(zssp::ReceiveResult::Rejected) => {}
@ -178,7 +178,7 @@ fn bob_main(
match context.receive( match context.receive(
bob_app, bob_app,
|| true, || true,
|s_public, _| Some((P384PublicKey::from_bytes(s_public).unwrap(), Secret::default(), ())), |s_public| Some((P384PublicKey::from_bytes(s_public).unwrap(), Secret::default(), ())),
|_, b| { |_, b| {
let _ = bob_out.send(b.to_vec()); let _ = bob_out.send(b.to_vec());
}, },
@ -204,7 +204,7 @@ fn bob_main(
.is_ok()); .is_ok());
transferred += data.len() as u64 * 2; // *2 because we are also sending this many bytes back transferred += data.len() as u64 * 2; // *2 because we are also sending this many bytes back
} }
Ok(zssp::ReceiveResult::OkNewSession(s)) => { Ok(zssp::ReceiveResult::OkNewSession(s, _)) => {
println!("[bob] new session {}", s.id.to_string()); println!("[bob] new session {}", s.id.to_string());
let _ = bob_session.replace(s); let _ = bob_session.replace(s);
} }

View file

@ -9,7 +9,7 @@
use std::mem::size_of; use std::mem::size_of;
use pqc_kyber::{KYBER_CIPHERTEXTBYTES, KYBER_PUBLICKEYBYTES}; use pqc_kyber::{KYBER_CIPHERTEXTBYTES, KYBER_PUBLICKEYBYTES};
use zerotier_crypto::hash::{HMAC_SHA384_SIZE, SHA384_HASH_SIZE}; use zerotier_crypto::hash::SHA384_HASH_SIZE;
use zerotier_crypto::p384::P384_PUBLIC_KEY_SIZE; use zerotier_crypto::p384::P384_PUBLIC_KEY_SIZE;
use crate::error::Error; use crate::error::Error;
@ -45,8 +45,7 @@ pub(crate) const HEADER_SIZE: usize = 16;
pub(crate) const HEADER_PROTECT_ENCRYPT_START: usize = 6; pub(crate) const HEADER_PROTECT_ENCRYPT_START: usize = 6;
pub(crate) const HEADER_PROTECT_ENCRYPT_END: usize = 22; pub(crate) const HEADER_PROTECT_ENCRYPT_END: usize = 22;
pub(crate) const KBKDF_KEY_USAGE_LABEL_INIT_ENCRYPTION: u8 = b'x'; // AES-CTR encryption during initial setup pub(crate) const KBKDF_KEY_USAGE_LABEL_INIT_ENCRYPTION: u8 = b'x'; // AES-GCM encryption during initial setup
pub(crate) const KBKDF_KEY_USAGE_LABEL_INIT_AUTHENTICATION: u8 = b'X'; // HMAC-SHA384 during initial setup
pub(crate) const KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB: u8 = b'A'; // AES-GCM in A->B direction pub(crate) const KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB: u8 = b'A'; // AES-GCM in A->B direction
pub(crate) const KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE: u8 = b'B'; // AES-GCM in B->A direction pub(crate) const KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE: u8 = b'B'; // AES-GCM in B->A direction
pub(crate) const KBKDF_KEY_USAGE_LABEL_RATCHET: u8 = b'R'; // Key used in derivatin of next session key pub(crate) const KBKDF_KEY_USAGE_LABEL_RATCHET: u8 = b'R'; // Key used in derivatin of next session key
@ -71,18 +70,18 @@ pub(crate) struct AliceNoiseXKInit {
pub header: [u8; HEADER_SIZE], pub header: [u8; HEADER_SIZE],
pub session_protocol_version: u8, pub session_protocol_version: u8,
pub alice_noise_e: [u8; P384_PUBLIC_KEY_SIZE], pub alice_noise_e: [u8; P384_PUBLIC_KEY_SIZE],
// -- start AES-CTR(es) encrypted section // -- start AES-GCM(es) encrypted section
pub alice_session_id: [u8; SessionId::SIZE], pub alice_session_id: [u8; SessionId::SIZE],
pub alice_hk_public: [u8; KYBER_PUBLICKEYBYTES], pub alice_hk_public: [u8; KYBER_PUBLICKEYBYTES],
pub header_protection_key: [u8; AES_HEADER_PROTECTION_KEY_SIZE], pub header_protection_key: [u8; AES_HEADER_PROTECTION_KEY_SIZE],
// -- end encrypted section // -- end encrypted section
pub hmac_es: [u8; HMAC_SHA384_SIZE], pub gcm_tag: [u8; AES_GCM_TAG_SIZE],
} }
impl AliceNoiseXKInit { impl AliceNoiseXKInit {
pub const ENC_START: usize = HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE; pub const ENC_START: usize = HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE;
pub const AUTH_START: usize = Self::ENC_START + SessionId::SIZE + KYBER_PUBLICKEYBYTES + AES_HEADER_PROTECTION_KEY_SIZE; pub const AUTH_START: usize = Self::ENC_START + SessionId::SIZE + KYBER_PUBLICKEYBYTES + AES_HEADER_PROTECTION_KEY_SIZE;
pub const SIZE: usize = Self::AUTH_START + HMAC_SHA384_SIZE; pub const SIZE: usize = Self::AUTH_START + AES_GCM_TAG_SIZE;
} }
/// The response to AliceNoiceXKInit containing Bob's ephemeral keys. /// The response to AliceNoiceXKInit containing Bob's ephemeral keys.
@ -92,17 +91,17 @@ pub(crate) struct BobNoiseXKAck {
pub header: [u8; HEADER_SIZE], pub header: [u8; HEADER_SIZE],
pub session_protocol_version: u8, pub session_protocol_version: u8,
pub bob_noise_e: [u8; P384_PUBLIC_KEY_SIZE], pub bob_noise_e: [u8; P384_PUBLIC_KEY_SIZE],
// -- start AES-CTR(es_ee) encrypted section // -- start AES-GCM(es_ee) encrypted section
pub bob_session_id: [u8; SessionId::SIZE], pub bob_session_id: [u8; SessionId::SIZE],
pub bob_hk_ciphertext: [u8; KYBER_CIPHERTEXTBYTES], pub bob_hk_ciphertext: [u8; KYBER_CIPHERTEXTBYTES],
// -- end encrypted sectiion // -- end encrypted sectiion
pub hmac_es_ee: [u8; HMAC_SHA384_SIZE], pub gcm_tag: [u8; AES_GCM_TAG_SIZE],
} }
impl BobNoiseXKAck { impl BobNoiseXKAck {
pub const ENC_START: usize = HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE; pub const ENC_START: usize = HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE;
pub const AUTH_START: usize = Self::ENC_START + SessionId::SIZE + KYBER_CIPHERTEXTBYTES; pub const AUTH_START: usize = Self::ENC_START + SessionId::SIZE + KYBER_CIPHERTEXTBYTES;
pub const SIZE: usize = Self::AUTH_START + HMAC_SHA384_SIZE; pub const SIZE: usize = Self::AUTH_START + AES_GCM_TAG_SIZE;
} }
/// Alice's final response containing her identity (she already knows Bob's) and meta-data. /// Alice's final response containing her identity (she already knows Bob's) and meta-data.
@ -112,20 +111,20 @@ impl BobNoiseXKAck {
pub(crate) struct AliceNoiseXKAck { pub(crate) struct AliceNoiseXKAck {
pub header: [u8; HEADER_SIZE], pub header: [u8; HEADER_SIZE],
pub session_protocol_version: u8, pub session_protocol_version: u8,
// -- start AES-CTR(es_ee_hk) encrypted section
pub alice_static_blob_length: [u8; 2], pub alice_static_blob_length: [u8; 2],
// -- start AES-GCM(es_ee_hk) encrypted section
pub alice_static_blob: [u8; ???], pub alice_static_blob: [u8; ???],
// -- end encrypted section
pub gcm_tag_0: [u8; AES_GCM_TAG_SIZE],
pub alice_metadata_length: [u8; 2], pub alice_metadata_length: [u8; 2],
// -- start AES-GCM(es_ee_se_hk_psk) encrypted section
pub alice_metadata: [u8; ???], pub alice_metadata: [u8; ???],
// -- end encrypted section // -- end encrypted section
pub hmac_es_ee: [u8; HMAC_SHA384_SIZE], pub gcm_tag_1: [u8; AES_GCM_TAG_SIZE],
pub hmac_es_ee_se_hk_psk: [u8; HMAC_SHA384_SIZE],
} }
*/ */
pub(crate) const ALICE_NOISE_XK_ACK_ENC_START: usize = HEADER_SIZE + 1; pub(crate) const ALICE_NOISE_XK_ACK_MIN_SIZE: usize = HEADER_SIZE + 1 + 2 + AES_GCM_TAG_SIZE + 2 + AES_GCM_TAG_SIZE;
pub(crate) const ALICE_NOISE_XK_ACK_AUTH_SIZE: usize = HMAC_SHA384_SIZE + HMAC_SHA384_SIZE;
pub(crate) const ALICE_NOISE_XK_ACK_MIN_SIZE: usize = ALICE_NOISE_XK_ACK_ENC_START + 2 + 2 + ALICE_NOISE_XK_ACK_AUTH_SIZE;
#[allow(unused)] #[allow(unused)]
#[repr(C, packed)] #[repr(C, packed)]
@ -135,7 +134,7 @@ pub(crate) struct RekeyInit {
// -- start AES-GCM encrypted portion (using current key) // -- start AES-GCM encrypted portion (using current key)
pub alice_e: [u8; P384_PUBLIC_KEY_SIZE], pub alice_e: [u8; P384_PUBLIC_KEY_SIZE],
// -- end AES-GCM encrypted portion // -- end AES-GCM encrypted portion
pub gcm_mac: [u8; AES_GCM_TAG_SIZE], pub gcm_tag: [u8; AES_GCM_TAG_SIZE],
} }
impl RekeyInit { impl RekeyInit {
@ -151,9 +150,9 @@ pub(crate) struct RekeyAck {
pub session_protocol_version: u8, pub session_protocol_version: u8,
// -- start AES-GCM encrypted portion (using current key) // -- start AES-GCM encrypted portion (using current key)
pub bob_e: [u8; P384_PUBLIC_KEY_SIZE], pub bob_e: [u8; P384_PUBLIC_KEY_SIZE],
pub next_key_fingerprint: [u8; SHA384_HASH_SIZE], pub next_key_fingerprint: [u8; SHA384_HASH_SIZE], // SHA384(next secret)
// -- end AES-GCM encrypted portion // -- end AES-GCM encrypted portion
pub gcm_mac: [u8; AES_GCM_TAG_SIZE], pub gcm_tag: [u8; AES_GCM_TAG_SIZE],
} }
impl RekeyAck { impl RekeyAck {

View file

@ -10,12 +10,13 @@
// FIPS compliant Noise_XK with Jedi powers (Kyber1024) and built-in attack-resistant large payload (fragmentation) support. // FIPS compliant Noise_XK with Jedi powers (Kyber1024) and built-in attack-resistant large payload (fragmentation) support.
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::io::Write;
use std::num::NonZeroU64; use std::num::NonZeroU64;
use std::sync::atomic::{AtomicI64, AtomicU64, Ordering}; use std::sync::atomic::{AtomicI64, AtomicU64, Ordering};
use std::sync::{Arc, Mutex, RwLock, Weak}; use std::sync::{Arc, Mutex, RwLock, Weak};
use zerotier_crypto::aes::{Aes, AesCtr, AesGcm}; use zerotier_crypto::aes::{Aes, AesGcm};
use zerotier_crypto::hash::{hmac_sha512, HMACSHA384, HMAC_SHA384_SIZE, SHA384}; use zerotier_crypto::hash::{hmac_sha512, SHA384};
use zerotier_crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE}; use zerotier_crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE};
use zerotier_crypto::secret::Secret; use zerotier_crypto::secret::Secret;
use zerotier_crypto::{random, secure_eq}; use zerotier_crypto::{random, secure_eq};
@ -63,8 +64,8 @@ pub enum ReceiveResult<'b, Application: ApplicationLayer> {
/// Packet was valid and a data payload was decoded and authenticated. /// Packet was valid and a data payload was decoded and authenticated.
OkData(Arc<Session<Application>>, &'b mut [u8]), OkData(Arc<Session<Application>>, &'b mut [u8]),
/// Packet was valid and a new session was created. /// Packet was valid and a new session was created, with optional attached meta-data.
OkNewSession(Arc<Session<Application>>), OkNewSession(Arc<Session<Application>>, Option<&'b mut [u8]>),
/// Packet appears valid but was rejected by the application layer, e.g. a rejected new session attempt. /// Packet appears valid but was rejected by the application layer, e.g. a rejected new session attempt.
Rejected, Rejected,
@ -339,24 +340,22 @@ impl<Application: ApplicationLayer> Context<Application> {
panic!(); // should be impossible panic!(); // should be impossible
}; };
let init: &mut AliceNoiseXKInit = byte_array_as_proto_buffer_mut(init_packet).unwrap(); {
init.session_protocol_version = SESSION_PROTOCOL_VERSION; let init: &mut AliceNoiseXKInit = byte_array_as_proto_buffer_mut(init_packet).unwrap();
init.alice_noise_e = alice_noise_e; init.session_protocol_version = SESSION_PROTOCOL_VERSION;
init.alice_session_id = *local_session_id.as_bytes(); init.alice_noise_e = alice_noise_e;
init.alice_hk_public = alice_hk_secret.public; init.alice_session_id = *local_session_id.as_bytes();
init.header_protection_key = header_protection_key.0; init.alice_hk_public = alice_hk_secret.public;
init.header_protection_key = header_protection_key.0;
}
aes_ctr_crypt_one_time_use_key( let mut gcm = AesGcm::new(
kbkdf::<AES_256_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_INIT_ENCRYPTION>(noise_es.as_bytes()).as_bytes(), kbkdf::<AES_256_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_INIT_ENCRYPTION>(noise_es.as_bytes()).as_bytes(),
&mut init_packet[AliceNoiseXKInit::ENC_START..AliceNoiseXKInit::AUTH_START], true,
); );
gcm.reset_init_gcm(&create_message_nonce(PACKET_TYPE_ALICE_NOISE_XK_INIT, 1));
let hmac = hmac_sha384_2( gcm.crypt_in_place(&mut init_packet[AliceNoiseXKInit::ENC_START..AliceNoiseXKInit::AUTH_START]);
kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_INIT_AUTHENTICATION>(noise_es.as_bytes()).as_bytes(), init_packet[AliceNoiseXKInit::AUTH_START..AliceNoiseXKInit::AUTH_START + AES_GCM_TAG_SIZE].copy_from_slice(&gcm.finish_encrypt());
&create_message_nonce(PACKET_TYPE_ALICE_NOISE_XK_INIT, 1),
&init_packet[HEADER_SIZE..AliceNoiseXKInit::AUTH_START],
);
init_packet[AliceNoiseXKInit::AUTH_START..AliceNoiseXKInit::AUTH_START + HMAC_SHA384_SIZE].copy_from_slice(&hmac);
send_with_fragmentation( send_with_fragmentation(
&mut send, &mut send,
@ -385,9 +384,9 @@ impl<Application: ApplicationLayer> Context<Application> {
/// with negotiation. False drops the packet. /// with negotiation. False drops the packet.
/// ///
/// The check_accept_session function is called at the end of negotiation for an incoming session /// The check_accept_session function is called at the end of negotiation for an incoming session
/// with the caller's static public blob and meta-data if any. It must return the P-384 static public /// with the caller's static public blob. It must return the P-384 static public key extracted from
/// key extracted from the supplied blob, a PSK (or all zeroes if none), and application data to /// the supplied blob, a PSK (or all zeroes if none), and application data to associate with the new
/// associate with the new session. A return of None abandons the session. /// session. A return of None rejects and abandons the session.
/// ///
/// Note that if check_accept_session accepts and returns Some() the session could still fail with /// Note that if check_accept_session accepts and returns Some() the session could still fail with
/// receive() returning an error. A Some() return from check_accept_sesion doesn't guarantee /// receive() returning an error. A Some() return from check_accept_sesion doesn't guarantee
@ -409,7 +408,7 @@ impl<Application: ApplicationLayer> Context<Application> {
'b, 'b,
SendFunction: FnMut(Option<&Arc<Session<Application>>>, &mut [u8]), SendFunction: FnMut(Option<&Arc<Session<Application>>>, &mut [u8]),
CheckAllowIncomingSession: FnMut() -> bool, CheckAllowIncomingSession: FnMut() -> bool,
CheckAcceptSession: FnMut(&[u8], Option<&[u8]>) -> Option<(P384PublicKey, Secret<64>, Application::Data)>, CheckAcceptSession: FnMut(&[u8]) -> Option<(P384PublicKey, Secret<64>, Application::Data)>,
>( >(
&self, &self,
app: &Application, app: &Application,
@ -568,7 +567,7 @@ impl<Application: ApplicationLayer> Context<Application> {
'b, 'b,
SendFunction: FnMut(Option<&Arc<Session<Application>>>, &mut [u8]), SendFunction: FnMut(Option<&Arc<Session<Application>>>, &mut [u8]),
CheckAllowIncomingSession: FnMut() -> bool, CheckAllowIncomingSession: FnMut() -> bool,
CheckAcceptSession: FnMut(&[u8], Option<&[u8]>) -> Option<(P384PublicKey, Secret<64>, Application::Data)>, CheckAcceptSession: FnMut(&[u8]) -> Option<(P384PublicKey, Secret<64>, Application::Data)>,
>( >(
&self, &self,
app: &Application, app: &Application,
@ -716,15 +715,14 @@ impl<Application: ApplicationLayer> Context<Application> {
let alice_noise_e = P384PublicKey::from_bytes(&pkt.alice_noise_e).ok_or(Error::FailedAuthentication)?; let alice_noise_e = P384PublicKey::from_bytes(&pkt.alice_noise_e).ok_or(Error::FailedAuthentication)?;
let noise_es = app.get_local_s_keypair().agree(&alice_noise_e).ok_or(Error::FailedAuthentication)?; let noise_es = app.get_local_s_keypair().agree(&alice_noise_e).ok_or(Error::FailedAuthentication)?;
// Authenticate packet and also prove that Alice knows our static public key. // Decrypt and authenticate init packet, also proving that caller knows our static identity.
if !secure_eq( let mut gcm = AesGcm::new(
&pkt.hmac_es, kbkdf::<AES_256_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_INIT_ENCRYPTION>(noise_es.as_bytes()).as_bytes(),
&hmac_sha384_2( false,
kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_INIT_AUTHENTICATION>(noise_es.as_bytes()).as_bytes(), );
&incoming_message_nonce, gcm.reset_init_gcm(&incoming_message_nonce);
&pkt_assembled[HEADER_SIZE..AliceNoiseXKInit::AUTH_START], gcm.crypt_in_place(&mut pkt_assembled[AliceNoiseXKInit::ENC_START..AliceNoiseXKInit::AUTH_START]);
), if !gcm.finish_decrypt(&pkt_assembled[AliceNoiseXKInit::AUTH_START..AliceNoiseXKInit::AUTH_START + AES_GCM_TAG_SIZE]) {
) {
return Err(Error::FailedAuthentication); return Err(Error::FailedAuthentication);
} }
@ -733,12 +731,6 @@ impl<Application: ApplicationLayer> Context<Application> {
return Ok(ReceiveResult::Rejected); return Ok(ReceiveResult::Rejected);
} }
// Decrypt encrypted part of payload.
aes_ctr_crypt_one_time_use_key(
kbkdf::<AES_256_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_INIT_ENCRYPTION>(noise_es.as_bytes()).as_bytes(),
&mut pkt_assembled[AliceNoiseXKInit::ENC_START..AliceNoiseXKInit::AUTH_START],
);
let pkt: &AliceNoiseXKInit = byte_array_as_proto_buffer(pkt_assembled)?; let pkt: &AliceNoiseXKInit = byte_array_as_proto_buffer(pkt_assembled)?;
let alice_session_id = SessionId::new_from_bytes(&pkt.alice_session_id).ok_or(Error::InvalidPacket)?; let alice_session_id = SessionId::new_from_bytes(&pkt.alice_session_id).ok_or(Error::InvalidPacket)?;
let header_protection_key = Secret(pkt.header_protection_key); let header_protection_key = Secret(pkt.header_protection_key);
@ -809,19 +801,14 @@ impl<Application: ApplicationLayer> Context<Application> {
ack.bob_session_id = *bob_session_id.as_bytes(); ack.bob_session_id = *bob_session_id.as_bytes();
ack.bob_hk_ciphertext = bob_hk_ciphertext; ack.bob_hk_ciphertext = bob_hk_ciphertext;
// Encrypt main section of reply. // Encrypt main section of reply and attach tag.
aes_ctr_crypt_one_time_use_key( let mut gcm = AesGcm::new(
kbkdf::<AES_256_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_INIT_ENCRYPTION>(noise_es_ee.as_bytes()).as_bytes(), kbkdf::<AES_256_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_INIT_ENCRYPTION>(noise_es_ee.as_bytes()).as_bytes(),
&mut ack_packet[BobNoiseXKAck::ENC_START..BobNoiseXKAck::AUTH_START], true,
); );
gcm.reset_init_gcm(&create_message_nonce(PACKET_TYPE_BOB_NOISE_XK_ACK, 1));
// Add HMAC-SHA384 to reply packet. gcm.crypt_in_place(&mut ack_packet[BobNoiseXKAck::ENC_START..BobNoiseXKAck::AUTH_START]);
let reply_hmac = hmac_sha384_2( ack_packet[BobNoiseXKAck::AUTH_START..BobNoiseXKAck::AUTH_START + AES_GCM_TAG_SIZE].copy_from_slice(&gcm.finish_encrypt());
kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_INIT_AUTHENTICATION>(noise_es_ee.as_bytes()).as_bytes(),
&create_message_nonce(PACKET_TYPE_BOB_NOISE_XK_ACK, 1),
&ack_packet[HEADER_SIZE..BobNoiseXKAck::AUTH_START],
);
ack_packet[BobNoiseXKAck::AUTH_START..].copy_from_slice(&reply_hmac);
send_with_fragmentation( send_with_fragmentation(
|b| send(None, b), |b| send(None, b),
@ -875,26 +862,17 @@ impl<Application: ApplicationLayer> Context<Application> {
.as_bytes(), .as_bytes(),
)); ));
let noise_es_ee_kex_hmac_key = // Decrypt and authenticate Bob's reply.
kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_INIT_AUTHENTICATION>(noise_es_ee.as_bytes()); let mut gcm = AesGcm::new(
kbkdf::<AES_256_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_INIT_ENCRYPTION>(noise_es_ee.as_bytes()).as_bytes(),
// Authenticate Bob's reply and the validity of bob_noise_e. false,
if !secure_eq( );
&pkt.hmac_es_ee, gcm.reset_init_gcm(&incoming_message_nonce);
&hmac_sha384_2( gcm.crypt_in_place(&mut pkt_assembled[BobNoiseXKAck::ENC_START..BobNoiseXKAck::AUTH_START]);
noise_es_ee_kex_hmac_key.as_bytes(), if !gcm.finish_decrypt(&pkt_assembled[BobNoiseXKAck::AUTH_START..BobNoiseXKAck::AUTH_START + AES_GCM_TAG_SIZE]) {
&incoming_message_nonce,
&pkt_assembled[HEADER_SIZE..BobNoiseXKAck::AUTH_START],
),
) {
return Err(Error::FailedAuthentication); return Err(Error::FailedAuthentication);
} }
// Decrypt encrypted portion of message.
aes_ctr_crypt_one_time_use_key(
kbkdf::<AES_256_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_INIT_ENCRYPTION>(noise_es_ee.as_bytes()).as_bytes(),
&mut pkt_assembled[BobNoiseXKAck::ENC_START..BobNoiseXKAck::AUTH_START],
);
let pkt: &BobNoiseXKAck = byte_array_as_proto_buffer(pkt_assembled)?; let pkt: &BobNoiseXKAck = byte_array_as_proto_buffer(pkt_assembled)?;
if let Some(bob_session_id) = SessionId::new_from_bytes(&pkt.bob_session_id) { if let Some(bob_session_id) = SessionId::new_from_bytes(&pkt.bob_session_id) {
@ -922,51 +900,47 @@ impl<Application: ApplicationLayer> Context<Application> {
// up forward secrecy. Also return Bob's opaque note. // up forward secrecy. Also return Bob's opaque note.
let mut reply_buffer = [0u8; MAX_NOISE_HANDSHAKE_SIZE]; let mut reply_buffer = [0u8; MAX_NOISE_HANDSHAKE_SIZE];
reply_buffer[HEADER_SIZE] = SESSION_PROTOCOL_VERSION; reply_buffer[HEADER_SIZE] = SESSION_PROTOCOL_VERSION;
let mut reply_len = HEADER_SIZE + 1; let mut rw = &mut reply_buffer[HEADER_SIZE + 1..];
let mut reply_buffer_append = |b: &[u8]| {
let reply_len_new = reply_len + b.len();
debug_assert!(reply_len_new <= MAX_NOISE_HANDSHAKE_SIZE);
reply_buffer[reply_len..reply_len_new].copy_from_slice(b);
reply_len = reply_len_new;
};
let alice_s_public_blob = app.get_local_s_public_blob(); let alice_s_public_blob = app.get_local_s_public_blob();
assert!(alice_s_public_blob.len() <= (u16::MAX as usize)); assert!(alice_s_public_blob.len() <= (u16::MAX as usize));
reply_buffer_append(&(alice_s_public_blob.len() as u16).to_le_bytes()); rw.write_all(&(alice_s_public_blob.len() as u16).to_le_bytes())?;
reply_buffer_append(alice_s_public_blob); let mut enc_start = MAX_NOISE_HANDSHAKE_SIZE - rw.len();
rw.write_all(alice_s_public_blob)?;
let mut reply_len = MAX_NOISE_HANDSHAKE_SIZE - rw.len();
let mut gcm = AesGcm::new(
kbkdf::<AES_256_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_INIT_ENCRYPTION>(&hmac_sha512(
noise_es_ee.as_bytes(),
hk.as_bytes(),
))
.as_bytes(),
true,
);
gcm.reset_init_gcm(&reply_message_nonce);
gcm.crypt_in_place(&mut reply_buffer[enc_start..reply_len]);
let mut rw = &mut reply_buffer[reply_len..];
rw.write_all(&gcm.finish_encrypt())?;
if let Some(md) = outgoing_offer.metadata.as_ref() { if let Some(md) = outgoing_offer.metadata.as_ref() {
reply_buffer_append(&(md.len() as u16).to_le_bytes()); assert!(md.len() <= (u16::MAX as usize));
reply_buffer_append(md.as_ref()); rw.write_all(&(md.len() as u16).to_le_bytes())?;
enc_start = MAX_NOISE_HANDSHAKE_SIZE - rw.len();
rw.write_all(md.as_ref())?;
} else { } else {
reply_buffer_append(&[0u8, 0u8]); // no meta-data rw.write_all(&[0u8, 0u8])?; // no meta-data
enc_start = MAX_NOISE_HANDSHAKE_SIZE - rw.len();
} }
// Encrypt Alice's static identity and other inner payload items. The key used here is reply_len = MAX_NOISE_HANDSHAKE_SIZE - rw.len();
// mixed with 'hk' to make identity secrecy PQ forward secure. let mut gcm = AesGcm::new(
aes_ctr_crypt_one_time_use_key( kbkdf::<AES_256_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_INIT_ENCRYPTION>(noise_es_ee_se_hk_psk.as_bytes()).as_bytes(),
&hmac_sha512(noise_es_ee.as_bytes(), hk.as_bytes())[..AES_256_KEY_SIZE], true,
&mut reply_buffer[HEADER_SIZE + 1..reply_len],
); );
gcm.reset_init_gcm(&reply_message_nonce);
// First attach HMAC allowing Bob to verify that this is from the same Alice and to gcm.crypt_in_place(&mut reply_buffer[enc_start..reply_len]);
// verify the authenticity of encrypted data. reply_buffer[reply_len..reply_len + AES_GCM_TAG_SIZE].copy_from_slice(&gcm.finish_encrypt());
let hmac_es_ee = hmac_sha384_2( reply_len += AES_GCM_TAG_SIZE;
noise_es_ee_kex_hmac_key.as_bytes(),
&reply_message_nonce,
&reply_buffer[HEADER_SIZE..reply_len],
);
reply_buffer[reply_len..reply_len + HMAC_SHA384_SIZE].copy_from_slice(&hmac_es_ee);
reply_len += HMAC_SHA384_SIZE;
// Then attach the final HMAC permitting Bob to verify the authenticity of the whole
// 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(
kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_INIT_AUTHENTICATION>(noise_es_ee_se_hk_psk.as_bytes()).as_bytes(),
&reply_message_nonce,
&reply_buffer[HEADER_SIZE..reply_len],
);
reply_buffer[reply_len..reply_len + HMAC_SHA384_SIZE].copy_from_slice(&hmac_es_ee_se_hk_psk);
reply_len += HMAC_SHA384_SIZE;
drop(state); drop(state);
{ {
@ -1025,62 +999,20 @@ impl<Application: ApplicationLayer> Context<Application> {
} }
if let Some(incoming) = incoming { if let Some(incoming) = incoming {
// Check the first HMAC to verify against the currently known noise_es_ee key, which verifies let mut r = PktReader(pkt_assembled, HEADER_SIZE + 1);
// that this reply is part of this session.
let auth_start = pkt_assembled.len() - ALICE_NOISE_XK_ACK_AUTH_SIZE;
if !secure_eq(
&pkt_assembled[auth_start..pkt_assembled.len() - HMAC_SHA384_SIZE],
&hmac_sha384_2(
kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_INIT_AUTHENTICATION>(incoming.noise_es_ee.as_bytes()).as_bytes(),
&incoming_message_nonce,
&pkt_assembled[HEADER_SIZE..auth_start],
),
) {
return Err(Error::FailedAuthentication);
}
// Make a copy of pkt_assembled so we can check the second HMAC against original ciphertext later. let alice_static_public_blob_size = r.read_u16()? as usize;
let mut pkt_assembly_buffer_copy = [0u8; MAX_NOISE_HANDSHAKE_SIZE]; let alice_static_public_blob = r.read_decrypt_auth(
pkt_assembly_buffer_copy[..pkt_assembled.len()].copy_from_slice(pkt_assembled); alice_static_public_blob_size,
kbkdf::<AES_256_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_INIT_ENCRYPTION>(&hmac_sha512(
incoming.noise_es_ee.as_bytes(),
incoming.hk.as_bytes(),
)),
&incoming_message_nonce,
)?;
// Decrypt encrypted section so we can finally learn Alice's static identity. // Check session acceptance and fish Alice's NIST P-384 static public key out of her static public blob.
aes_ctr_crypt_one_time_use_key( let check_result = check_accept_session(alice_static_public_blob);
&hmac_sha512(incoming.noise_es_ee.as_bytes(), incoming.hk.as_bytes())[..AES_256_KEY_SIZE],
&mut pkt_assembled[ALICE_NOISE_XK_ACK_ENC_START..auth_start],
);
// Read the static public blob and optional meta-data.
let mut pkt_assembled_ptr = HEADER_SIZE + 1;
let mut pkt_assembled_field_end = pkt_assembled_ptr + 2;
if pkt_assembled_field_end >= pkt_assembled.len() {
return Err(Error::InvalidPacket);
}
let alice_static_public_blob_size =
u16::from_le_bytes(pkt_assembled[pkt_assembled_ptr..pkt_assembled_field_end].try_into().unwrap()) as usize;
pkt_assembled_ptr = pkt_assembled_field_end;
pkt_assembled_field_end = pkt_assembled_ptr + alice_static_public_blob_size;
if pkt_assembled_field_end >= pkt_assembled.len() {
return Err(Error::InvalidPacket);
}
let alice_static_public_blob = &pkt_assembled[pkt_assembled_ptr..pkt_assembled_field_end];
pkt_assembled_ptr = pkt_assembled_field_end;
pkt_assembled_field_end = pkt_assembled_ptr + 2;
if pkt_assembled_field_end >= pkt_assembled.len() {
return Err(Error::InvalidPacket);
}
let alice_meta_data_size =
u16::from_le_bytes(pkt_assembled[pkt_assembled_ptr..pkt_assembled_field_end].try_into().unwrap()) as usize;
pkt_assembled_ptr = pkt_assembled_field_end;
pkt_assembled_field_end = pkt_assembled_ptr + alice_meta_data_size;
let alice_meta_data = if alice_meta_data_size > 0 {
Some(&pkt_assembled[pkt_assembled_ptr..pkt_assembled_field_end])
} else {
None
};
// Check session acceptance and fish Alice's NIST P-384 static public key out of
// her static public blob.
let check_result = check_accept_session(alice_static_public_blob, alice_meta_data);
if check_result.is_none() { if check_result.is_none() {
self.sessions.write().unwrap().incoming.remove(&incoming.bob_session_id); self.sessions.write().unwrap().incoming.remove(&incoming.bob_session_id);
return Ok(ReceiveResult::Rejected); return Ok(ReceiveResult::Rejected);
@ -1100,17 +1032,18 @@ impl<Application: ApplicationLayer> Context<Application> {
&hmac_sha512(psk.as_bytes(), incoming.hk.as_bytes()), &hmac_sha512(psk.as_bytes(), incoming.hk.as_bytes()),
)); ));
// Verify the packet using the final key to verify the whole key exchange. // Decrypt meta-data and verify the final key in the process. Copy meta-data
if !secure_eq( // into the temporary data buffer to return.
&pkt_assembly_buffer_copy[auth_start + HMAC_SHA384_SIZE..pkt_assembled.len()], let alice_meta_data_size = r.read_u16()? as usize;
&hmac_sha384_2( let alice_meta_data = r.read_decrypt_auth(
kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_INIT_AUTHENTICATION>(noise_es_ee_se_hk_psk.as_bytes()).as_bytes(), alice_meta_data_size,
&incoming_message_nonce, kbkdf::<AES_256_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_INIT_ENCRYPTION>(noise_es_ee_se_hk_psk.as_bytes()),
&pkt_assembly_buffer_copy[HEADER_SIZE..auth_start + HMAC_SHA384_SIZE], &incoming_message_nonce,
), )?;
) { if alice_meta_data.len() > data_buf.len() {
return Err(Error::FailedAuthentication); return Err(Error::DataTooLarge);
} }
data_buf[..alice_meta_data.len()].copy_from_slice(alice_meta_data);
let session = Arc::new(Session { let session = Arc::new(Session {
id: incoming.bob_session_id, id: incoming.bob_session_id,
@ -1131,6 +1064,7 @@ impl<Application: ApplicationLayer> Context<Application> {
defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())),
}); });
// Promote incoming session to active.
{ {
let mut sessions = self.sessions.write().unwrap(); let mut sessions = self.sessions.write().unwrap();
sessions.incoming.remove(&incoming.bob_session_id); sessions.incoming.remove(&incoming.bob_session_id);
@ -1139,7 +1073,14 @@ impl<Application: ApplicationLayer> Context<Application> {
let _ = session.send_nop(|b| send(Some(&session), b)); let _ = session.send_nop(|b| send(Some(&session), b));
return Ok(ReceiveResult::OkNewSession(session)); return Ok(ReceiveResult::OkNewSession(
session,
if alice_meta_data.is_empty() {
None
} else {
Some(&mut data_buf[..alice_meta_data.len()])
},
));
} else { } else {
return Err(Error::UnknownLocalSessionId); return Err(Error::UnknownLocalSessionId);
} }
@ -1650,22 +1591,40 @@ impl SessionKey {
} }
} }
/// Shortcut to HMAC data split into two slices. /// Helper for parsing variable length ALICE_NOISE_XK_ACK
fn hmac_sha384_2(key: &[u8], a: &[u8], b: &[u8]) -> [u8; 48] { struct PktReader<'a>(&'a mut [u8], usize);
let mut hmac = HMACSHA384::new(key);
hmac.update(a);
hmac.update(b);
hmac.finish()
}
/// Shortcut to AES-CTR encrypt or decrypt with a zero IV. impl<'a> PktReader<'a> {
/// fn read_u16(&mut self) -> Result<u16, Error> {
/// This is used during Noise_XK handshaking. Each stage uses a different key to encrypt the let tmp = self.1 + 2;
/// payload that is used only once per handshake and per session. if tmp <= self.0.len() {
fn aes_ctr_crypt_one_time_use_key(key: &[u8], data: &mut [u8]) { let n = u16::from_le_bytes(self.0[self.1..tmp].try_into().unwrap());
let mut ctr = AesCtr::new(key); self.1 = tmp;
ctr.reset_set_iv(&[0u8; 12]); Ok(n)
ctr.crypt_in_place(data); } else {
Err(Error::InvalidPacket)
}
}
fn read_decrypt_auth<'b>(&'b mut self, l: usize, k: Secret<AES_256_KEY_SIZE>, nonce: &[u8]) -> Result<&'b [u8], Error> {
let mut tmp = self.1 + l;
if (tmp + AES_GCM_TAG_SIZE) <= self.0.len() {
let mut gcm = AesGcm::new(k.as_bytes(), false);
gcm.reset_init_gcm(nonce);
gcm.crypt_in_place(&mut self.0[self.1..tmp]);
let s = &self.0[self.1..tmp];
self.1 = tmp;
tmp += AES_GCM_TAG_SIZE;
if !gcm.finish_decrypt(&self.0[self.1..tmp]) {
Err(Error::FailedAuthentication)
} else {
self.1 = tmp;
Ok(s)
}
} else {
Err(Error::InvalidPacket)
}
}
} }
/// HMAC-SHA512 key derivation based on: https://csrc.nist.gov/publications/detail/sp/800-108/final (page 7) /// HMAC-SHA512 key derivation based on: https://csrc.nist.gov/publications/detail/sp/800-108/final (page 7)