mirror of
https://github.com/ZeroTier/ZeroTierOne
synced 2025-08-22 22:33:58 -07:00
Noise XK work in progress.
This commit is contained in:
parent
8eedf70a1f
commit
dfc9d5c4e0
7 changed files with 371 additions and 35 deletions
|
@ -7,6 +7,8 @@ use std::ptr::null;
|
|||
|
||||
pub const SHA512_HASH_SIZE: usize = 64;
|
||||
pub const SHA384_HASH_SIZE: usize = 48;
|
||||
pub const HMAC_SHA512_SIZE: usize = 64;
|
||||
pub const HMAC_SHA384_SIZE: usize = 48;
|
||||
|
||||
pub struct SHA512(Option<openssl::sha::Sha512>);
|
||||
|
||||
|
|
|
@ -62,6 +62,9 @@ pub trait ApplicationLayer: Sized {
|
|||
/// Rate limit and check an attempted new session (called before accept_new_session).
|
||||
fn check_new_session(&self, rc: &ReceiveContext<Self>, remote_address: &Self::RemoteAddress) -> bool;
|
||||
|
||||
/// Get a new locally unique session ID.
|
||||
fn new_session(&self, remote_address: &Self::RemoteAddress) -> Option<(SessionId, Self::Data)>;
|
||||
|
||||
/// Check whether a new session should be accepted.
|
||||
///
|
||||
/// On success a tuple of local session ID, static secret, and associated object is returned. The
|
||||
|
|
|
@ -7,19 +7,6 @@ pub const MIN_TRANSPORT_MTU: usize = 64;
|
|||
/// Minimum recommended interval between calls to service() on each session, in milliseconds.
|
||||
pub const SERVICE_INTERVAL: u64 = 10000;
|
||||
|
||||
/// Setting this to true enables kyber1024 post-quantum forward secrecy.
|
||||
///
|
||||
/// Kyber1024 is used for data forward secrecy but not authentication. Authentication would
|
||||
/// require Kyber1024 in identities, which would make them huge, and isn't needed for our
|
||||
/// threat model which is data warehousing today to decrypt tomorrow. Breaking authentication
|
||||
/// is only relevant today, not in some mid to far future where a QC that can break 384-bit ECC
|
||||
/// exists.
|
||||
///
|
||||
/// This is normally enabled but could be disabled at build time for e.g. very small devices.
|
||||
/// It might not even be necessary there to disable it since it's not that big and is usually
|
||||
/// faster than NIST P-384 ECDH.
|
||||
pub(crate) const JEDI: bool = true;
|
||||
|
||||
/// Maximum number of fragments for data packets.
|
||||
pub(crate) const MAX_FRAGMENTS: usize = 48; // hard protocol max: 63
|
||||
|
||||
|
@ -61,14 +48,10 @@ pub(crate) const HEADER_CHECK_ENCRYPT_START: usize = 6;
|
|||
/// End of single block AES encryption of a portion of the header (and some data).
|
||||
pub(crate) const HEADER_CHECK_ENCRYPT_END: usize = 22;
|
||||
|
||||
/// Size of AES-GCM keys (256 bits)
|
||||
pub(crate) const AES_KEY_SIZE: usize = 32;
|
||||
|
||||
/// Size of AES-GCM MAC tags
|
||||
pub(crate) const AES_HEADER_CHECK_KEY_SIZE: usize = 16;
|
||||
pub(crate) const AES_GCM_TAG_SIZE: usize = 16;
|
||||
|
||||
/// Size of HMAC-SHA384 MAC tags
|
||||
pub(crate) const HMAC_SIZE: usize = 48;
|
||||
pub(crate) const AES_GCM_NONCE_SIZE: usize = 12;
|
||||
|
||||
/// Size of a session ID, which behaves a bit like a TCP port number.
|
||||
///
|
||||
|
@ -84,17 +67,11 @@ pub(crate) const COUNTER_WINDOW_MAX_OUT_OF_ORDER: usize = 16;
|
|||
/// the counter yields an invalid value.
|
||||
pub(crate) const COUNTER_WINDOW_MAX_SKIP_AHEAD: u64 = 16777216;
|
||||
|
||||
// Packet types can range from 0 to 15 (4 bits) -- 0-3 are defined and 4-15 are reserved for future use
|
||||
pub(crate) const PACKET_TYPE_DATA: u8 = 0;
|
||||
pub(crate) const PACKET_TYPE_INITIAL_KEY_OFFER: u8 = 1; // "alice"
|
||||
pub(crate) const PACKET_TYPE_KEY_COUNTER_OFFER: u8 = 2; // "bob"
|
||||
|
||||
// Key usage labels for sub-key derivation using NIST-style KBKDF (basically just HMAC KDF).
|
||||
pub(crate) const KBKDF_KEY_USAGE_LABEL_HMAC: u8 = b'M'; // HMAC-SHA384 authentication for key exchanges
|
||||
pub(crate) const KBKDF_KEY_USAGE_LABEL_KEX_PAYLOAD_ENCRYPTION: u8 = b'M'; // 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
|
||||
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_RATCHETING: u8 = b'R'; // Key input for next ephemeral ratcheting
|
||||
|
||||
// AES key size for header check code generation
|
||||
pub(crate) const HEADER_CHECK_AES_KEY_SIZE: usize = 16;
|
||||
|
@ -106,8 +83,8 @@ 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_IKpsk2_NISTP384_?KYBER1024_AESGCM_SHA512' | shasum -a 512 | cut -d ' ' -f 1 | xxd -r -p | xxd -i
|
||||
0x35, 0x6a, 0x75, 0xc0, 0xbf, 0xbe, 0xc3, 0x59, 0x70, 0x94, 0x50, 0x69, 0x4c, 0xa2, 0x08, 0x40, 0xc7, 0xdf, 0x67, 0xa8, 0x68, 0x52,
|
||||
0x6e, 0xd5, 0xdd, 0x77, 0xec, 0x59, 0x6f, 0x8e, 0xa1, 0x99, 0xb4, 0x32, 0x85, 0xaf, 0x7f, 0x0d, 0xa9, 0x6c, 0x01, 0xfb, 0x72, 0x46,
|
||||
0xc0, 0x09, 0x58, 0xb8, 0xe0, 0xa8, 0xcf, 0xb1, 0x58, 0x04, 0x6e, 0x32, 0xba, 0xa8, 0xb8, 0xf9, 0x0a, 0xa4, 0xbf, 0x36,
|
||||
// echo -n 'ZSSP_Noise_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,
|
||||
];
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
mod applicationlayer;
|
||||
mod error;
|
||||
mod proto;
|
||||
mod sessionid;
|
||||
mod tests;
|
||||
mod zssp;
|
||||
|
|
154
zssp/src/proto.rs
Normal file
154
zssp/src/proto.rs
Normal file
|
@ -0,0 +1,154 @@
|
|||
use std::mem::size_of;
|
||||
|
||||
use pqc_kyber::{KYBER_CIPHERTEXTBYTES, KYBER_PUBLICKEYBYTES};
|
||||
use zerotier_crypto::p384::P384_PUBLIC_KEY_SIZE;
|
||||
|
||||
use crate::applicationlayer::ApplicationLayer;
|
||||
use crate::constants::{AES_GCM_TAG_SIZE, HEADER_SIZE, MIN_PACKET_SIZE, SESSION_ID_SIZE};
|
||||
use crate::error::Error;
|
||||
|
||||
/// Maximum packet size for handshake packets
|
||||
///
|
||||
/// Packed structs are padded to this size so they can be recast to byte arrays of this size.
|
||||
pub(crate) const NOISE_MAX_HANDSHAKE_PACKET_SIZE: usize = 2048;
|
||||
|
||||
pub(crate) const PACKET_TYPE_DATA: u8 = 0;
|
||||
pub(crate) const PACKET_TYPE_ALICE_EPHEMERAL_OFFER: u8 = 1;
|
||||
pub(crate) const PACKET_TYPE_BOB_EPHEMERAL_COUNTER_OFFER: u8 = 2;
|
||||
pub(crate) const PACKET_TYPE_ALICE_STATIC_ACK: u8 = 3;
|
||||
pub(crate) const PACKET_TYPE_ALICE_REKEY_INIT: u8 = 4;
|
||||
pub(crate) const PACKET_TYPE_BOB_REKEY_ACK: u8 = 5;
|
||||
|
||||
pub(crate) const NOISE_XK_ALICE_EPHEMERAL_OFFER_ENCRYPTED_SECTION_START: usize = HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE;
|
||||
pub(crate) const NOISE_XK_ALICE_EPHEMERAL_OFFER_ENCRYPTED_SECTION_END: usize =
|
||||
NOISE_XK_ALICE_EPHEMERAL_OFFER_ENCRYPTED_SECTION_START + SESSION_ID_SIZE + KYBER_PUBLICKEYBYTES + 8;
|
||||
pub(crate) const NOISE_XK_ALICE_EPHEMERAL_OFFER_SIZE: usize = NOISE_XK_ALICE_EPHEMERAL_OFFER_ENCRYPTED_SECTION_END + AES_GCM_TAG_SIZE;
|
||||
|
||||
pub(crate) trait ProtocolFlatBuffer {}
|
||||
|
||||
#[allow(unused)]
|
||||
#[repr(C, packed)]
|
||||
pub(crate) struct NoiseXKAliceEphemeralOffer {
|
||||
pub header: [u8; HEADER_SIZE],
|
||||
pub session_protocol_version: u8,
|
||||
pub alice_noise_e: [u8; P384_PUBLIC_KEY_SIZE],
|
||||
// -- start AES-GCM(es) encrypted section (IV is first 12 bytes of SHA384(alice_noise_e))
|
||||
pub alice_session_id: [u8; SESSION_ID_SIZE],
|
||||
pub alice_hk_public: [u8; KYBER_PUBLICKEYBYTES],
|
||||
pub salt: [u8; 8],
|
||||
// -- end encrypted section
|
||||
pub gcm_mac: [u8; 16],
|
||||
_padding: [u8; NOISE_MAX_HANDSHAKE_PACKET_SIZE - NOISE_XK_ALICE_EPHEMERAL_OFFER_SIZE],
|
||||
}
|
||||
|
||||
pub(crate) const NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_ENCRYPTED_SECTION_START: usize = HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE;
|
||||
pub(crate) const NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_ENCRYPTED_SECTION_END: usize =
|
||||
NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_ENCRYPTED_SECTION_START + SESSION_ID_SIZE + KYBER_CIPHERTEXTBYTES;
|
||||
pub(crate) const NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_SIZE: usize =
|
||||
NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_ENCRYPTED_SECTION_END + AES_GCM_TAG_SIZE;
|
||||
|
||||
#[allow(unused)]
|
||||
#[repr(C, packed)]
|
||||
pub(crate) struct NoiseXKBobEphemeralCounterOffer {
|
||||
pub header: [u8; HEADER_SIZE],
|
||||
pub session_protocol_version: u8,
|
||||
pub bob_noise_e: [u8; P384_PUBLIC_KEY_SIZE],
|
||||
// -- start AES-GCM(es_ee) encrypted section (IV is first 12 bytes of SHA384(bob_noise_e))
|
||||
pub bob_session_id: [u8; SESSION_ID_SIZE],
|
||||
pub bob_hk_ciphertext: [u8; KYBER_CIPHERTEXTBYTES],
|
||||
// -- end encrypted sectiion
|
||||
pub gcm_mac: [u8; 16],
|
||||
_padding: [u8; NOISE_MAX_HANDSHAKE_PACKET_SIZE - NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_SIZE],
|
||||
}
|
||||
|
||||
/*
|
||||
pub(crate) const NOISE_XK_ALICE_STATIC_ACK_FIXED_FIELDS_SIZE: usize = HEADER_SIZE + 1;
|
||||
|
||||
#[allow(unused)]
|
||||
#[repr(C, packed)]
|
||||
pub(crate) struct NoiseXKAliceStaticAck {
|
||||
pub header: [u8; HEADER_SIZE],
|
||||
pub session_protocol_version: u8,
|
||||
// -- start AES-GCM(es_ee) encrypted section (IV is first 12 bytes of SHA384(hk))
|
||||
_var_length_fields_and_padding: [u8; NOISE_MAX_HANDSHAKE_PACKET_SIZE - NOISE_XK_ALICE_STATIC_ACK_FIXED_FIELDS_SIZE],
|
||||
// alice_static_blob_length: u16,
|
||||
// alice_static_blob: [u8; ???],
|
||||
// alice_metadata_length: u16,
|
||||
// alice_metadata: [u8; ???],
|
||||
// hmac_es_ee_se_hk_psk: [u8; HMAC_SHA384_SIZE],
|
||||
// -- end encrypted section
|
||||
// pub gcm_mac: [u8; 16],
|
||||
}
|
||||
*/
|
||||
|
||||
#[allow(unused)]
|
||||
#[repr(C, packed)]
|
||||
pub(crate) struct AliceRekeyInit {
|
||||
pub header: [u8; HEADER_SIZE],
|
||||
// -- start AES-GCM encrypted portion (using current key)
|
||||
pub alice_e: [u8; P384_PUBLIC_KEY_SIZE],
|
||||
pub alice_hk_public: [u8; KYBER_PUBLICKEYBYTES],
|
||||
// -- end AES-GCM encrypted portion
|
||||
pub gcm_mac: [u8; 16],
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[repr(C, packed)]
|
||||
pub(crate) struct BobRekeyAck {
|
||||
pub header: [u8; HEADER_SIZE],
|
||||
// -- start AES-GCM encrypted portion (using current key)
|
||||
pub bob_e: [u8; P384_PUBLIC_KEY_SIZE],
|
||||
pub bob_hk_ciphertext: [u8; KYBER_CIPHERTEXTBYTES],
|
||||
// -- end AES-GCM encrypted portion
|
||||
pub gcm_mac: [u8; 16],
|
||||
}
|
||||
|
||||
// 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.
|
||||
impl ProtocolFlatBuffer for NoiseXKAliceEphemeralOffer {}
|
||||
impl ProtocolFlatBuffer for NoiseXKBobEphemeralCounterOffer {}
|
||||
//impl ProtocolFlatBuffer for NoiseXKAliceStaticAck {}
|
||||
impl ProtocolFlatBuffer for AliceRekeyInit {}
|
||||
impl ProtocolFlatBuffer for BobRekeyAck {}
|
||||
|
||||
/// 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);
|
||||
}
|
||||
|
||||
// Down here is where the only unsafe code here lives. It's instrumented with assertions wherever
|
||||
// possible and just helps us efficiently cast to/from flat buffers.
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn new_packet_buffer<B: ProtocolFlatBuffer>() -> B {
|
||||
unsafe { std::mem::zeroed() }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn packet_buffer_as_bytes<B: ProtocolFlatBuffer>(b: &B) -> &[u8; NOISE_MAX_HANDSHAKE_PACKET_SIZE] {
|
||||
assert_eq!(size_of::<B>(), NOISE_MAX_HANDSHAKE_PACKET_SIZE);
|
||||
unsafe { &*(b as *const B).cast() }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn packet_buffer_as_bytes_mut<B: ProtocolFlatBuffer>(b: &mut B) -> &mut [u8; NOISE_MAX_HANDSHAKE_PACKET_SIZE] {
|
||||
assert_eq!(size_of::<B>(), NOISE_MAX_HANDSHAKE_PACKET_SIZE);
|
||||
unsafe { &mut *(b as *mut B).cast() }
|
||||
}
|
|
@ -25,6 +25,13 @@ impl SessionId {
|
|||
Self(NonZeroU64::new(((random::xorshift64_random() % (Self::MAX - 1)) + 1).to_le()).unwrap())
|
||||
}
|
||||
|
||||
pub(crate) fn new_from_bytes(b: &[u8; SESSION_ID_SIZE]) -> Option<SessionId> {
|
||||
let mut tmp = [0u8; 8];
|
||||
tmp[..SESSION_ID_SIZE].copy_from_slice(b);
|
||||
Self::new_from_u64_le(u64::from_ne_bytes(tmp))
|
||||
}
|
||||
|
||||
/// Create from a u64 that is already in little-endian byte order.
|
||||
#[inline(always)]
|
||||
pub(crate) fn new_from_u64_le(i: u64) -> Option<SessionId> {
|
||||
NonZeroU64::new(i & Self::MAX.to_le()).map(|i| Self(i))
|
||||
|
|
202
zssp/src/zssp.rs
202
zssp/src/zssp.rs
|
@ -3,9 +3,12 @@
|
|||
// ZSSP: ZeroTier Secure Session Protocol
|
||||
// FIPS compliant Noise_IK with Jedi powers and built-in attack-resistant large payload (fragmentation) support.
|
||||
|
||||
use std::io::Write;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::{Mutex, RwLock};
|
||||
|
||||
use pqc_kyber::KYBER_SECRETKEYBYTES;
|
||||
|
||||
use zerotier_crypto::aes::{Aes, AesGcm};
|
||||
use zerotier_crypto::hash::{hmac_sha512, HMACSHA384, SHA384};
|
||||
use zerotier_crypto::p384::{P384KeyPair, P384PublicKey, P384_PUBLIC_KEY_SIZE};
|
||||
|
@ -22,6 +25,7 @@ use zerotier_utils::varint;
|
|||
use crate::applicationlayer::ApplicationLayer;
|
||||
use crate::constants::*;
|
||||
use crate::error::Error;
|
||||
use crate::proto::*;
|
||||
use crate::sessionid::SessionId;
|
||||
|
||||
/// Result generated by the packet receive function, with possible payloads.
|
||||
|
@ -63,16 +67,31 @@ pub struct Session<Application: ApplicationLayer> {
|
|||
|
||||
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
|
||||
psk: Secret<64>, // Arbitrary PSK provided by external code
|
||||
noise_ss: Secret<48>, // Static raw shared ECDH NIST P-384 key
|
||||
header_check_cipher: Aes, // Cipher used for header check codes (not Noise related)
|
||||
state: RwLock<SessionMutableState>, // Mutable parts of state (other than defrag buffers)
|
||||
remote_s_public_blob_hash: [u8; 48], // SHA384(remote static public key blob)
|
||||
remote_s_public_p384_bytes: [u8; P384_PUBLIC_KEY_SIZE], // Remote NIST P-384 static public key
|
||||
offer: Mutex<EphemeralOffer>, // Most recently sent ephemeral offer
|
||||
state: RwLock<State>, // Miscellaneous mutable state
|
||||
|
||||
//psk: Secret<64>, // Arbitrary PSK provided by external code
|
||||
//noise_ss: Secret<48>, // Static raw shared ECDH NIST P-384 key
|
||||
//state: RwLock<SessionMutableState>, // Mutable parts of state (other than defrag buffers)
|
||||
//remote_s_public_blob_hash: [u8; 48], // SHA384(remote static public key blob)
|
||||
//remote_s_public_p384_bytes: [u8; P384_PUBLIC_KEY_SIZE], // Remote NIST P-384 static public key
|
||||
defrag: Mutex<RingBufferMap<u64, GatherArray<Application::IncomingPacketBuffer, MAX_FRAGMENTS>, 8, 8>>,
|
||||
}
|
||||
|
||||
enum EphemeralOffer {
|
||||
None,
|
||||
Alice(Secret<64>, P384KeyPair, [u8; KYBER_SECRETKEYBYTES]),
|
||||
Bob(Secret<64>, P384KeyPair),
|
||||
}
|
||||
|
||||
struct State {
|
||||
keys: [Option<SessionKey>; 2],
|
||||
current_key: usize,
|
||||
psk: Secret<64>,
|
||||
}
|
||||
|
||||
/*
|
||||
struct SessionMutableState {
|
||||
remote_session_id: Option<SessionId>, // The other side's 48-bit session ID
|
||||
session_keys: [Option<SessionKey>; 2], // Buffers to store last and latest key by 1-bit key index
|
||||
|
@ -80,6 +99,7 @@ struct SessionMutableState {
|
|||
offer: Option<EphemeralOffer>, // Most recent ephemeral offer sent to remote
|
||||
last_remote_offer: i64, // Time of most recent ephemeral offer (ms)
|
||||
}
|
||||
*/
|
||||
|
||||
/// A shared symmetric session key.
|
||||
struct SessionKey {
|
||||
|
@ -98,6 +118,7 @@ struct SessionKey {
|
|||
jedi: bool, // True if Kyber1024 was used (both sides enabled)
|
||||
}
|
||||
|
||||
/*
|
||||
/// Alice's KEY_OFFER, remembered so Noise agreement process can resume on KEY_COUNTER_OFFER.
|
||||
struct EphemeralOffer {
|
||||
id: [u8; 16], // Arbitrary random offer ID
|
||||
|
@ -108,6 +129,7 @@ struct EphemeralOffer {
|
|||
alice_e_keypair: P384KeyPair, // NIST P-384 key pair (Noise ephemeral key for Alice)
|
||||
alice_hk_keypair: Option<pqc_kyber::Keypair>, // Kyber1024 key pair (PQ hybrid ephemeral key for Alice)
|
||||
}
|
||||
*/
|
||||
|
||||
/// Was this side the one who sent the first offer (Alice) or countered (Bob).
|
||||
///
|
||||
|
@ -130,6 +152,7 @@ impl<Application: ApplicationLayer> Session<Application> {
|
|||
/// * `application_data` - Arbitrary object to put into session
|
||||
/// * `mtu` - Physical wire maximum transmission unit (current value, can change through the course of a session)
|
||||
/// * `current_time` - Current monotonic time in milliseconds since an arbitrary time in the past
|
||||
/*
|
||||
pub fn start_new<SendFunction: FnMut(&mut [u8])>(
|
||||
app: &Application,
|
||||
mut send: SendFunction,
|
||||
|
@ -190,12 +213,14 @@ impl<Application: ApplicationLayer> Session<Application> {
|
|||
}
|
||||
return Err(Error::InvalidParameter);
|
||||
}
|
||||
*/
|
||||
|
||||
/// Send data over the session.
|
||||
///
|
||||
/// * `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,
|
||||
|
@ -253,7 +278,9 @@ impl<Application: ApplicationLayer> Session<Application> {
|
|||
}
|
||||
return Err(Error::SessionNotEstablished);
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
/// Check whether this session is established.
|
||||
pub fn established(&self) -> bool {
|
||||
let state = self.state.read().unwrap();
|
||||
|
@ -267,6 +294,7 @@ impl<Application: ApplicationLayer> Session<Application> {
|
|||
.as_ref()
|
||||
.map(|k| (k.secret_fingerprint, k.ratchet_count, k.role, k.jedi))
|
||||
}
|
||||
*/
|
||||
|
||||
/// This function needs to be called on each session at least every SERVICE_INTERVAL milliseconds.
|
||||
///
|
||||
|
@ -285,6 +313,7 @@ impl<Application: ApplicationLayer> Session<Application> {
|
|||
current_time: i64,
|
||||
force_expire: bool,
|
||||
) {
|
||||
/*
|
||||
let state = self.state.read().unwrap();
|
||||
if state.session_keys[state.cur_session_key_idx].as_ref().map_or(true, |k| {
|
||||
matches!(k.role, Role::Bob)
|
||||
|
@ -323,6 +352,7 @@ impl<Application: ApplicationLayer> Session<Application> {
|
|||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/// Check the receive window without mutating state.
|
||||
|
@ -509,6 +539,7 @@ 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() {
|
||||
|
@ -589,9 +620,169 @@ impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
|||
unlikely_branch();
|
||||
return Err(Error::SessionNotEstablished);
|
||||
}
|
||||
*/
|
||||
todo!()
|
||||
} else {
|
||||
unlikely_branch();
|
||||
|
||||
match packet_type {
|
||||
PACKET_TYPE_ALICE_EPHEMERAL_OFFER => {
|
||||
// Alice (remote) --> Bob (local)
|
||||
|
||||
let mut pkt: NoiseXKAliceEphemeralOffer = new_packet_buffer();
|
||||
if assemble_fragments_into::<Application>(fragments, packet_buffer_as_bytes_mut(&mut pkt))?
|
||||
!= NOISE_XK_ALICE_EPHEMERAL_OFFER_SIZE
|
||||
{
|
||||
return Err(Error::InvalidPacket);
|
||||
}
|
||||
if pkt.session_protocol_version != SESSION_PROTOCOL_VERSION {
|
||||
return Err(Error::UnknownProtocolVersion);
|
||||
}
|
||||
|
||||
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 mut gcm = AesGcm::new(
|
||||
&kbkdf512(noise_es.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_PAYLOAD_ENCRYPTION).as_bytes()[..AES_KEY_SIZE],
|
||||
false,
|
||||
);
|
||||
gcm.reset_init_gcm(&SHA384::hash(&pkt.alice_noise_e)[..AES_GCM_NONCE_SIZE]);
|
||||
gcm.crypt_in_place(
|
||||
&mut packet_buffer_as_bytes_mut(&mut pkt)
|
||||
[NOISE_XK_ALICE_EPHEMERAL_OFFER_ENCRYPTED_SECTION_START..NOISE_XK_ALICE_EPHEMERAL_OFFER_ENCRYPTED_SECTION_END],
|
||||
);
|
||||
if !gcm.finish_decrypt(&pkt.gcm_mac) {
|
||||
return Err(Error::FailedAuthentication);
|
||||
}
|
||||
|
||||
let alice_session_id = SessionId::new_from_bytes(&pkt.alice_session_id).ok_or(Error::InvalidPacket)?;
|
||||
let (bob_hk_ciphertext, hk) = pqc_kyber::encapsulate(&pkt.alice_hk_public, &mut random::SecureRandom::default())
|
||||
.map_err(|_| Error::FailedAuthentication)?;
|
||||
|
||||
let (bob_session_id, application_data) = app.new_session(remote_address).ok_or(Error::NewSessionRejected)?;
|
||||
let bob_e_secret = P384KeyPair::generate();
|
||||
|
||||
let noise_es_ee = Secret(hmac_sha512(
|
||||
noise_es.as_bytes(),
|
||||
bob_e_secret.agree(&alice_noise_e).ok_or(Error::FailedAuthentication)?.as_bytes(),
|
||||
));
|
||||
|
||||
let mut reply: NoiseXKBobEphemeralCounterOffer = new_packet_buffer();
|
||||
reply.session_protocol_version = SESSION_PROTOCOL_VERSION;
|
||||
reply.bob_noise_e = bob_e_secret.public_key_bytes().clone();
|
||||
reply.bob_session_id = bob_session_id.as_bytes().clone();
|
||||
reply.bob_hk_ciphertext = bob_hk_ciphertext;
|
||||
|
||||
let mut gcm = AesGcm::new(
|
||||
&kbkdf512(noise_es_ee.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_PAYLOAD_ENCRYPTION).as_bytes()[..AES_KEY_SIZE],
|
||||
true,
|
||||
);
|
||||
gcm.reset_init_gcm(&SHA384::hash(bob_e_secret.public_key_bytes())[..AES_GCM_NONCE_SIZE]);
|
||||
gcm.crypt_in_place(
|
||||
&mut packet_buffer_as_bytes_mut(&mut reply)[NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_ENCRYPTED_SECTION_START
|
||||
..NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_ENCRYPTED_SECTION_END],
|
||||
);
|
||||
reply.gcm_mac = gcm.finish_encrypt();
|
||||
|
||||
let header_check_cipher = Aes::new(
|
||||
&kbkdf512(noise_es.as_bytes(), KBKDF_KEY_USAGE_LABEL_HEADER_CHECK).as_bytes()[..AES_HEADER_CHECK_KEY_SIZE],
|
||||
);
|
||||
|
||||
send_with_fragmentation(
|
||||
send,
|
||||
&mut packet_buffer_as_bytes_mut(&mut reply)[..NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_SIZE],
|
||||
mtu,
|
||||
PACKET_TYPE_BOB_EPHEMERAL_COUNTER_OFFER,
|
||||
u64::from(alice_session_id),
|
||||
0,
|
||||
1,
|
||||
&header_check_cipher,
|
||||
)?;
|
||||
|
||||
return Ok(ReceiveResult::OkNewSession(Session {
|
||||
id: bob_session_id,
|
||||
application_data,
|
||||
send_counter: AtomicU64::new(2), // 1 was used in reply
|
||||
receive_window: std::array::from_fn(|_| AtomicU64::new(0)),
|
||||
header_check_cipher,
|
||||
offer: Mutex::new(EphemeralOffer::Bob(noise_es_ee, bob_e_secret)),
|
||||
state: RwLock::new(State { keys: [None, None], current_key: 0, psk: Secret::default() }),
|
||||
defrag: Mutex::new(RingBufferMap::new(random::xorshift64_random() as u32)),
|
||||
}));
|
||||
}
|
||||
|
||||
PACKET_TYPE_BOB_EPHEMERAL_COUNTER_OFFER => {
|
||||
// Bob (remote) --> Alice (local)
|
||||
|
||||
if let Some(session) = session {
|
||||
let mut pkt: NoiseXKBobEphemeralCounterOffer = new_packet_buffer();
|
||||
if assemble_fragments_into::<Application>(fragments, packet_buffer_as_bytes_mut(&mut pkt))?
|
||||
!= NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_SIZE
|
||||
{
|
||||
return Err(Error::InvalidPacket);
|
||||
}
|
||||
if pkt.session_protocol_version != SESSION_PROTOCOL_VERSION {
|
||||
return Err(Error::UnknownProtocolVersion);
|
||||
}
|
||||
|
||||
let mut offer = session.offer.lock().unwrap();
|
||||
match &*offer {
|
||||
EphemeralOffer::Alice(noise_es, alice_e_secret, alice_hk_secret) => {
|
||||
let bob_noise_e = P384PublicKey::from_bytes(&pkt.bob_noise_e).ok_or(Error::FailedAuthentication)?;
|
||||
let noise_es_ee = Secret(hmac_sha512(
|
||||
noise_es.as_bytes(),
|
||||
alice_e_secret.agree(&bob_noise_e).ok_or(Error::FailedAuthentication)?.as_bytes(),
|
||||
));
|
||||
let noise_es_ee_se = app.get_local_s_keypair().agree(&bob_noise_e).ok_or(Error::FailedAuthentication)?;
|
||||
|
||||
let mut gcm = AesGcm::new(
|
||||
&kbkdf512(noise_es_ee.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_PAYLOAD_ENCRYPTION).as_bytes()
|
||||
[..AES_KEY_SIZE],
|
||||
false,
|
||||
);
|
||||
gcm.reset_init_gcm(&SHA384::hash(&pkt.bob_noise_e)[..AES_GCM_NONCE_SIZE]);
|
||||
gcm.crypt_in_place(
|
||||
&mut packet_buffer_as_bytes_mut(&mut pkt)[NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_ENCRYPTED_SECTION_START
|
||||
..NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_ENCRYPTED_SECTION_END],
|
||||
);
|
||||
if !gcm.finish_decrypt(&pkt.gcm_mac) {
|
||||
return Err(Error::FailedAuthentication);
|
||||
}
|
||||
|
||||
let bob_session_id = SessionId::new_from_bytes(&pkt.bob_session_id).ok_or(Error::InvalidPacket)?;
|
||||
let hk = pqc_kyber::decapsulate(&pkt.bob_hk_ciphertext, alice_hk_secret)
|
||||
.map_err(|_| Error::FailedAuthentication)?;
|
||||
|
||||
let alice_s_public_blob = app.get_local_s_public_blob();
|
||||
|
||||
let mut reply = [0u8; NOISE_MAX_HANDSHAKE_PACKET_SIZE];
|
||||
let mut reply_w = &mut reply[..];
|
||||
assert!(alice_s_public_blob.len() <= (u16::MAX as usize));
|
||||
let _ = reply_w.write_all(&(alice_s_public_blob.len() as u16).to_le_bytes());
|
||||
let _ = reply_w.write_all(alice_s_public_blob);
|
||||
let _ = reply_w.write_all(&[0u8, 0u8]); // zero size meta-data, to be implemented later
|
||||
}
|
||||
_ => {
|
||||
return Ok(ReceiveResult::Ignored);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(Error::SessionNotEstablished);
|
||||
}
|
||||
}
|
||||
|
||||
PACKET_TYPE_ALICE_STATIC_ACK => {}
|
||||
|
||||
PACKET_TYPE_ALICE_REKEY_INIT => {}
|
||||
|
||||
PACKET_TYPE_BOB_REKEY_ACK => {}
|
||||
|
||||
_ => {
|
||||
return Err(Error::InvalidPacket);
|
||||
}
|
||||
}
|
||||
todo!()
|
||||
/*
|
||||
// To greatly simplify logic handling key exchange packets, assemble these first.
|
||||
// Handling KEX packets isn't the fast path so the extra copying isn't significant.
|
||||
const KEX_BUF_LEN: usize = 4096;
|
||||
|
@ -1081,6 +1272,7 @@ impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
|||
|
||||
_ => return Err(Error::InvalidPacket),
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue