mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-06-07 21:13:44 +02:00
rustfmt
This commit is contained in:
parent
bd737e8ddb
commit
2b35a1b2e7
7 changed files with 119 additions and 114 deletions
|
@ -2,34 +2,11 @@
|
||||||
authors = ["ZeroTier, Inc. <contact@zerotier.com>", "Adam Ierymenko <adam.ierymenko@zerotier.com>"]
|
authors = ["ZeroTier, Inc. <contact@zerotier.com>", "Adam Ierymenko <adam.ierymenko@zerotier.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
name = "zerotier-zssp"
|
name = "zssp"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
zerotier-utils = { path = "../utils" }
|
zerotier-utils = { path = "../utils" }
|
||||||
zerotier-crypto = { path = "../crypto" }
|
zerotier-crypto = { path = "../crypto" }
|
||||||
pqc_kyber = { path = "../third_party/kyber", features = ["kyber1024", "reference"], default-features = false }
|
pqc_kyber = { path = "../third_party/kyber", features = ["kyber1024", "reference"], default-features = false }
|
||||||
#ed25519-dalek = { version = "1.0.1", features = ["std", "u64_backend"], default-features = false }
|
|
||||||
#foreign-types = "0.3.1"
|
|
||||||
#lazy_static = "^1"
|
|
||||||
#poly1305 = { version = "0.8.0", features = [], default-features = false }
|
|
||||||
#pqc_kyber = { path = "../third_party/kyber", features = ["kyber1024", "reference"], default-features = false }
|
|
||||||
#pqc_kyber = { version = "^0", features = ["kyber1024", "reference"], default-features = false }
|
#pqc_kyber = { version = "^0", features = ["kyber1024", "reference"], default-features = false }
|
||||||
#rand_core = "0.5.1"
|
|
||||||
#rand_core_062 = { package = "rand_core", version = "0.6.2" }
|
|
||||||
#subtle = "2.4.1"
|
|
||||||
#x25519-dalek = { version = "1.2.0", features = ["std", "u64_backend"], default-features = false }
|
|
||||||
|
|
||||||
#[target."cfg(windows)".dependencies]
|
|
||||||
#openssl = { version = "^0", features = ["vendored"], default-features = false }
|
|
||||||
#winapi = { version = "^0", features = ["handleapi", "ws2ipdef", "ws2tcpip"] }
|
|
||||||
|
|
||||||
#[target."cfg(not(windows))".dependencies]
|
|
||||||
#openssl = { version = "^0", features = [], default-features = false }
|
|
||||||
#libc = "^0"
|
|
||||||
#signal-hook = "^0"
|
|
||||||
|
|
||||||
#[dev-dependencies]
|
|
||||||
#criterion = "0.3"
|
|
||||||
#sha2 = "^0"
|
|
||||||
#hex-literal = "^0"
|
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
use zerotier_crypto::{p384::{P384KeyPair, P384PublicKey}, secret::Secret};
|
use zerotier_crypto::{
|
||||||
|
p384::{P384KeyPair, P384PublicKey},
|
||||||
use crate::{zssp::{Session, ReceiveContext}, ints::SessionId};
|
secret::Secret,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
ints::SessionId,
|
||||||
|
zssp::{ReceiveContext, Session},
|
||||||
|
};
|
||||||
|
|
||||||
/// Trait to implement to integrate the session into an application.
|
/// Trait to implement to integrate the session into an application.
|
||||||
///
|
///
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
/// Minimum size of a valid physical ZSSP packet or packet fragment.
|
/// Minimum size of a valid physical ZSSP packet or packet fragment.
|
||||||
pub const MIN_PACKET_SIZE: usize = HEADER_SIZE + AES_GCM_TAG_SIZE;
|
pub const MIN_PACKET_SIZE: usize = HEADER_SIZE + AES_GCM_TAG_SIZE;
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
use std::{sync::atomic::{AtomicU64, Ordering}};
|
|
||||||
|
|
||||||
use zerotier_crypto::random;
|
use zerotier_crypto::random;
|
||||||
use zerotier_utils::memory;
|
use zerotier_utils::memory;
|
||||||
|
|
||||||
|
|
||||||
/// "Canonical header" for generating 96-bit AES-GCM nonce and for inclusion in HMACs.
|
/// "Canonical header" for generating 96-bit AES-GCM nonce and for inclusion in HMACs.
|
||||||
///
|
///
|
||||||
/// This is basically the actual header but with fragment count and fragment total set to zero.
|
/// This is basically the actual header but with fragment count and fragment total set to zero.
|
||||||
|
@ -50,7 +48,6 @@ impl SessionId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new_random() -> Self {
|
pub fn new_random() -> Self {
|
||||||
Self(random::next_u64_secure() % Self::NIL.0)
|
Self(random::next_u64_secure() % Self::NIL.0)
|
||||||
|
@ -64,8 +61,6 @@ impl From<SessionId> for u64 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// Outgoing packet counter with strictly ordered atomic semantics.
|
/// Outgoing packet counter with strictly ordered atomic semantics.
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub(crate) struct Counter(AtomicU64);
|
pub(crate) struct Counter(AtomicU64);
|
||||||
|
@ -109,7 +104,6 @@ impl CounterValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Was this side the one who sent the first offer (Alice) or countered (Bob).
|
/// Was this side the one who sent the first offer (Alice) or countered (Bob).
|
||||||
/// Note that role is not fixed. Either side can take either role. It's just who
|
/// Note that role is not fixed. Either side can take either role. It's just who
|
||||||
/// initiated first.
|
/// initiated first.
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
|
|
||||||
mod zssp;
|
|
||||||
mod app_layer;
|
mod app_layer;
|
||||||
mod ints;
|
mod ints;
|
||||||
mod tests;
|
mod tests;
|
||||||
|
mod zssp;
|
||||||
|
|
||||||
pub mod constants;
|
pub mod constants;
|
||||||
pub use zssp::{Error, ReceiveResult, ReceiveContext, Session};
|
|
||||||
pub use app_layer::ApplicationLayer;
|
pub use app_layer::ApplicationLayer;
|
||||||
pub use ints::{SessionId, Role};
|
pub use ints::{Role, SessionId};
|
||||||
|
pub use zssp::{Error, ReceiveContext, ReceiveResult, Session};
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::collections::LinkedList;
|
use std::collections::LinkedList;
|
||||||
|
|
180
zssp/src/zssp.rs
180
zssp/src/zssp.rs
|
@ -18,9 +18,8 @@ use zerotier_utils::unlikely_branch;
|
||||||
use zerotier_utils::varint;
|
use zerotier_utils::varint;
|
||||||
|
|
||||||
use crate::app_layer::ApplicationLayer;
|
use crate::app_layer::ApplicationLayer;
|
||||||
use crate::ints::*;
|
|
||||||
use crate::constants::*;
|
use crate::constants::*;
|
||||||
|
use crate::ints::*;
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////
|
||||||
// types
|
// types
|
||||||
|
@ -101,13 +100,13 @@ pub struct Session<Layer: ApplicationLayer> {
|
||||||
/// An arbitrary object associated with session (type defined in Host trait)
|
/// An arbitrary object associated with session (type defined in Host trait)
|
||||||
pub user_data: Layer::SessionUserData,
|
pub user_data: Layer::SessionUserData,
|
||||||
|
|
||||||
send_counter: Counter, // Outgoing packet counter and nonce state
|
send_counter: Counter, // Outgoing packet counter and nonce state
|
||||||
psk: Secret<64>, // Arbitrary PSK provided by external code
|
psk: Secret<64>, // Arbitrary PSK provided by external code
|
||||||
noise_ss: Secret<48>, // Static raw shared ECDH NIST P-384 key
|
noise_ss: Secret<48>, // Static raw shared ECDH NIST P-384 key
|
||||||
header_check_cipher: Aes, // Cipher used for header MAC (fragmentation)
|
header_check_cipher: Aes, // Cipher used for header MAC (fragmentation)
|
||||||
state: RwLock<SessionMutableState>, // Mutable parts of state (other than defrag buffers)
|
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_blob_hash: [u8; 48], // SHA384(remote static public key blob)
|
||||||
remote_s_public_raw: [u8; P384_PUBLIC_KEY_SIZE], // Remote NIST P-384 static public key
|
remote_s_public_raw: [u8; P384_PUBLIC_KEY_SIZE], // Remote NIST P-384 static public key
|
||||||
|
|
||||||
defrag: Mutex<RingBufferMap<u32, GatherArray<Layer::IncomingPacketBuffer, MAX_FRAGMENTS>, 8, 8>>,
|
defrag: Mutex<RingBufferMap<u32, GatherArray<Layer::IncomingPacketBuffer, MAX_FRAGMENTS>, 8, 8>>,
|
||||||
}
|
}
|
||||||
|
@ -116,7 +115,7 @@ struct SessionMutableState {
|
||||||
remote_session_id: Option<SessionId>, // The other side's 48-bit session ID
|
remote_session_id: Option<SessionId>, // The other side's 48-bit session ID
|
||||||
session_keys: [Option<SessionKey>; KEY_HISTORY_SIZE], // Buffers to store current, next, and last active key
|
session_keys: [Option<SessionKey>; KEY_HISTORY_SIZE], // Buffers to store current, next, and last active key
|
||||||
cur_session_key_idx: usize, // Pointer used for keys[] circular buffer
|
cur_session_key_idx: usize, // Pointer used for keys[] circular buffer
|
||||||
offer: Option<EphemeralOffer>, // Most recent ephemeral offer sent to remote
|
offer: Option<EphemeralOffer>, // Most recent ephemeral offer sent to remote
|
||||||
last_remote_offer: i64, // Time of most recent ephemeral offer (ms)
|
last_remote_offer: i64, // Time of most recent ephemeral offer (ms)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,8 +152,6 @@ struct KeyLifetime {
|
||||||
rekey_at_or_after_timestamp: i64,
|
rekey_at_or_after_timestamp: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////
|
||||||
// functions
|
// functions
|
||||||
////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////
|
||||||
|
@ -186,12 +183,11 @@ impl std::fmt::Debug for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Write src into buffer starting at the index idx. If buffer cannot fit src at that location, nothing at all is written and Error::UnexpectedBufferOverrun is returned. No other errors can be returned by this function. An idx incremented by the amount written is returned.
|
/// Write src into buffer starting at the index idx. If buffer cannot fit src at that location, nothing at all is written and Error::UnexpectedBufferOverrun is returned. No other errors can be returned by this function. An idx incremented by the amount written is returned.
|
||||||
fn safe_write_all(buffer: &mut [u8], idx: usize, src: &[u8]) -> Result<usize, Error> {
|
fn safe_write_all(buffer: &mut [u8], idx: usize, src: &[u8]) -> Result<usize, Error> {
|
||||||
let dest = &mut buffer[idx..];
|
let dest = &mut buffer[idx..];
|
||||||
let amt = src.len();
|
let amt = src.len();
|
||||||
if dest.len() >= amt {
|
if dest.len() >= amt {
|
||||||
dest[..amt].copy_from_slice(src);
|
dest[..amt].copy_from_slice(src);
|
||||||
Ok(idx + amt)
|
Ok(idx + amt)
|
||||||
} else {
|
} else {
|
||||||
|
@ -227,7 +223,6 @@ fn varint_safe_read(src: &mut &[u8]) -> Result<u64, Error> {
|
||||||
Ok(v)
|
Ok(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl<Layer: ApplicationLayer> Session<Layer> {
|
impl<Layer: ApplicationLayer> Session<Layer> {
|
||||||
/// Create a new session and send an initial key offer message to the other end.
|
/// Create a new session and send an initial key offer message to the other end.
|
||||||
///
|
///
|
||||||
|
@ -256,7 +251,7 @@ impl<Layer: ApplicationLayer> Session<Layer> {
|
||||||
let send_counter = Counter::new();
|
let send_counter = Counter::new();
|
||||||
let bob_s_public_blob_hash = SHA384::hash(bob_s_public_blob);
|
let bob_s_public_blob_hash = SHA384::hash(bob_s_public_blob);
|
||||||
let header_check_cipher =
|
let header_check_cipher =
|
||||||
Aes::new(kbkdf512(noise_ss.as_bytes(), KBKDF_KEY_USAGE_LABEL_HEADER_CHECK).first_n::<HEADER_CHECK_AES_KEY_SIZE>());
|
Aes::new(kbkdf512(noise_ss.as_bytes(), KBKDF_KEY_USAGE_LABEL_HEADER_CHECK).first_n::<HEADER_CHECK_AES_KEY_SIZE>());
|
||||||
let mut offer = None;
|
let mut offer = None;
|
||||||
if send_ephemeral_offer(
|
if send_ephemeral_offer(
|
||||||
&mut send,
|
&mut send,
|
||||||
|
@ -272,8 +267,10 @@ impl<Layer: ApplicationLayer> Session<Layer> {
|
||||||
None,
|
None,
|
||||||
mtu,
|
mtu,
|
||||||
current_time,
|
current_time,
|
||||||
&mut offer
|
&mut offer,
|
||||||
).is_ok() {
|
)
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
return Ok(Self {
|
return Ok(Self {
|
||||||
id: local_session_id,
|
id: local_session_id,
|
||||||
user_data,
|
user_data,
|
||||||
|
@ -424,16 +421,15 @@ impl<Layer: ApplicationLayer> Session<Layer> {
|
||||||
force_rekey: bool,
|
force_rekey: bool,
|
||||||
) {
|
) {
|
||||||
let state = self.state.read().unwrap();
|
let state = self.state.read().unwrap();
|
||||||
if (
|
if (force_rekey
|
||||||
force_rekey
|
|
||||||
|| state.session_keys[state.cur_session_key_idx]
|
|| state.session_keys[state.cur_session_key_idx]
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or(true, |key| key.lifetime.should_rekey(self.send_counter.previous(), current_time)))
|
.map_or(true, |key| key.lifetime.should_rekey(self.send_counter.previous(), current_time)))
|
||||||
&& state
|
&& state
|
||||||
.offer
|
.offer
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or(true, |o| (current_time - o.creation_time) > Layer::REKEY_RATE_LIMIT_MS
|
.map_or(true, |o| (current_time - o.creation_time) > Layer::REKEY_RATE_LIMIT_MS)
|
||||||
) {
|
{
|
||||||
if let Some(remote_s_public) = P384PublicKey::from_bytes(&self.remote_s_public_raw) {
|
if let Some(remote_s_public) = P384PublicKey::from_bytes(&self.remote_s_public_raw) {
|
||||||
let mut offer = None;
|
let mut offer = None;
|
||||||
if send_ephemeral_offer(
|
if send_ephemeral_offer(
|
||||||
|
@ -454,8 +450,10 @@ impl<Layer: ApplicationLayer> Session<Layer> {
|
||||||
},
|
},
|
||||||
mtu,
|
mtu,
|
||||||
current_time,
|
current_time,
|
||||||
&mut offer
|
&mut offer,
|
||||||
).is_ok() {
|
)
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
drop(state);
|
drop(state);
|
||||||
let _ = self.state.write().unwrap().offer.replace(offer.unwrap());
|
let _ = self.state.write().unwrap().offer.replace(offer.unwrap());
|
||||||
}
|
}
|
||||||
|
@ -679,9 +677,9 @@ impl<Layer: ApplicationLayer> ReceiveContext<Layer> {
|
||||||
if aead_authentication_ok {
|
if aead_authentication_ok {
|
||||||
// Select this key as the new default if it's newer than the current key.
|
// Select this key as the new default if it's newer than the current key.
|
||||||
if p > 0
|
if p > 0
|
||||||
&& state.session_keys[state.cur_session_key_idx]
|
&& state.session_keys[state.cur_session_key_idx]
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or(true, |old| old.establish_counter < session_key.establish_counter)
|
.map_or(true, |old| old.establish_counter < session_key.establish_counter)
|
||||||
{
|
{
|
||||||
drop(state);
|
drop(state);
|
||||||
let mut state = session.state.write().unwrap();
|
let mut state = session.state.write().unwrap();
|
||||||
|
@ -780,11 +778,18 @@ impl<Layer: ApplicationLayer> ReceiveContext<Layer> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Key agreement: alice (remote) ephemeral NIST P-384 <> local static NIST P-384
|
// Key agreement: alice (remote) ephemeral NIST P-384 <> local static NIST P-384
|
||||||
let alice_e_public = P384PublicKey::from_bytes(&kex_packet[(HEADER_SIZE + 1)..plaintext_end]).ok_or(Error::FailedAuthentication)?;
|
let alice_e_public =
|
||||||
let noise_es = host.get_local_s_keypair().agree(&alice_e_public).ok_or(Error::FailedAuthentication)?;
|
P384PublicKey::from_bytes(&kex_packet[(HEADER_SIZE + 1)..plaintext_end]).ok_or(Error::FailedAuthentication)?;
|
||||||
|
let noise_es = host
|
||||||
|
.get_local_s_keypair()
|
||||||
|
.agree(&alice_e_public)
|
||||||
|
.ok_or(Error::FailedAuthentication)?;
|
||||||
|
|
||||||
// Initial key derivation from starting point, mixing in alice's ephemeral public and the es.
|
// Initial key derivation from starting point, mixing in alice's ephemeral public and the es.
|
||||||
let es_key = Secret(hmac_sha512(&hmac_sha512(&INITIAL_KEY, alice_e_public.as_bytes()), noise_es.as_bytes()));
|
let es_key = Secret(hmac_sha512(
|
||||||
|
&hmac_sha512(&INITIAL_KEY, alice_e_public.as_bytes()),
|
||||||
|
noise_es.as_bytes(),
|
||||||
|
));
|
||||||
|
|
||||||
// Decrypt the encrypted part of the packet payload and authenticate the above key exchange via AES-GCM auth.
|
// Decrypt the encrypted part of the packet payload and authenticate the above key exchange via AES-GCM auth.
|
||||||
let mut c = AesGcm::new(
|
let mut c = AesGcm::new(
|
||||||
|
@ -799,8 +804,14 @@ impl<Layer: ApplicationLayer> ReceiveContext<Layer> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse payload and get alice's session ID, alice's public blob, metadata, and (if present) Alice's Kyber1024 public.
|
// Parse payload and get alice's session ID, alice's public blob, metadata, and (if present) Alice's Kyber1024 public.
|
||||||
let (offer_id, alice_session_id, alice_s_public_blob, alice_metadata, alice_hk_public_raw, alice_ratchet_key_fingerprint) =
|
let (
|
||||||
parse_dec_key_offer_after_header(&kex_packet[plaintext_end..kex_packet_len], packet_type)?;
|
offer_id,
|
||||||
|
alice_session_id,
|
||||||
|
alice_s_public_blob,
|
||||||
|
alice_metadata,
|
||||||
|
alice_hk_public_raw,
|
||||||
|
alice_ratchet_key_fingerprint,
|
||||||
|
) = parse_dec_key_offer_after_header(&kex_packet[plaintext_end..kex_packet_len], packet_type)?;
|
||||||
|
|
||||||
// We either have a session, in which case they should have supplied a ratchet key fingerprint, or
|
// We either have a session, in which case they should have supplied a ratchet key fingerprint, or
|
||||||
// we don't and they should not have supplied one.
|
// we don't and they should not have supplied one.
|
||||||
|
@ -865,7 +876,7 @@ impl<Layer: ApplicationLayer> ReceiveContext<Layer> {
|
||||||
(None, ratchet_key, ratchet_count)
|
(None, ratchet_key, ratchet_count)
|
||||||
} else {
|
} else {
|
||||||
if let Some((new_session_id, psk, associated_object)) =
|
if let Some((new_session_id, psk, associated_object)) =
|
||||||
host.accept_new_session(self, remote_address, alice_s_public_blob, alice_metadata)
|
host.accept_new_session(self, remote_address, alice_s_public_blob, alice_metadata)
|
||||||
{
|
{
|
||||||
let header_check_cipher = Aes::new(
|
let header_check_cipher = Aes::new(
|
||||||
kbkdf512(noise_ss.as_bytes(), KBKDF_KEY_USAGE_LABEL_HEADER_CHECK).first_n::<HEADER_CHECK_AES_KEY_SIZE>(),
|
kbkdf512(noise_ss.as_bytes(), KBKDF_KEY_USAGE_LABEL_HEADER_CHECK).first_n::<HEADER_CHECK_AES_KEY_SIZE>(),
|
||||||
|
@ -919,7 +930,10 @@ impl<Layer: ApplicationLayer> ReceiveContext<Layer> {
|
||||||
let noise_ik_key = Secret(hmac_sha512(
|
let noise_ik_key = Secret(hmac_sha512(
|
||||||
session.psk.as_bytes(),
|
session.psk.as_bytes(),
|
||||||
&hmac_sha512(
|
&hmac_sha512(
|
||||||
&hmac_sha512(&hmac_sha512(ss_key.as_bytes(), bob_e_keypair.public_key_bytes()), noise_ee.as_bytes()),
|
&hmac_sha512(
|
||||||
|
&hmac_sha512(ss_key.as_bytes(), bob_e_keypair.public_key_bytes()),
|
||||||
|
noise_ee.as_bytes(),
|
||||||
|
),
|
||||||
noise_se.as_bytes(),
|
noise_se.as_bytes(),
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
@ -929,7 +943,9 @@ impl<Layer: ApplicationLayer> ReceiveContext<Layer> {
|
||||||
|
|
||||||
// Generate a Kyber encapsulated ciphertext if Kyber is enabled and the other side sent us a public key.
|
// Generate a Kyber encapsulated ciphertext if Kyber is enabled and the other side sent us a public key.
|
||||||
let (bob_hk_public, hybrid_kk) = if JEDI && alice_hk_public_raw.len() > 0 {
|
let (bob_hk_public, hybrid_kk) = if JEDI && alice_hk_public_raw.len() > 0 {
|
||||||
if let Ok((bob_hk_public, hybrid_kk)) = pqc_kyber::encapsulate(alice_hk_public_raw, &mut random::SecureRandom::default()) {
|
if let Ok((bob_hk_public, hybrid_kk)) =
|
||||||
|
pqc_kyber::encapsulate(alice_hk_public_raw, &mut random::SecureRandom::default())
|
||||||
|
{
|
||||||
(Some(bob_hk_public), Some(Secret(hybrid_kk)))
|
(Some(bob_hk_public), Some(Secret(hybrid_kk)))
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::FailedAuthentication);
|
return Err(Error::FailedAuthentication);
|
||||||
|
@ -1012,7 +1028,14 @@ impl<Layer: ApplicationLayer> ReceiveContext<Layer> {
|
||||||
idx = safe_write_all(&mut reply_buf, idx, &hmac)?;
|
idx = safe_write_all(&mut reply_buf, idx, &hmac)?;
|
||||||
let packet_end = idx;
|
let packet_end = idx;
|
||||||
|
|
||||||
let session_key = SessionKey::new(session_key, Role::Bob, current_time, reply_counter, ratchet_count + 1, hybrid_kk.is_some());
|
let session_key = SessionKey::new(
|
||||||
|
session_key,
|
||||||
|
Role::Bob,
|
||||||
|
current_time,
|
||||||
|
reply_counter,
|
||||||
|
ratchet_count + 1,
|
||||||
|
hybrid_kk.is_some(),
|
||||||
|
);
|
||||||
|
|
||||||
let mut state = session.state.write().unwrap();
|
let mut state = session.state.write().unwrap();
|
||||||
let _ = state.remote_session_id.replace(alice_session_id);
|
let _ = state.remote_session_id.replace(alice_session_id);
|
||||||
|
@ -1048,13 +1071,10 @@ impl<Layer: ApplicationLayer> ReceiveContext<Layer> {
|
||||||
if let Some(session) = session {
|
if let Some(session) = session {
|
||||||
let state = session.state.read().unwrap();
|
let state = session.state.read().unwrap();
|
||||||
if let Some(offer) = state.offer.as_ref() {
|
if let Some(offer) = state.offer.as_ref() {
|
||||||
|
let bob_e_public = P384PublicKey::from_bytes(&kex_packet[(HEADER_SIZE + 1)..plaintext_end])
|
||||||
let bob_e_public = P384PublicKey::from_bytes(&kex_packet[(HEADER_SIZE + 1)..plaintext_end]).ok_or(Error::FailedAuthentication)?;
|
|
||||||
let noise_ee = offer.alice_e_keypair.agree(&bob_e_public).ok_or(Error::FailedAuthentication)?;
|
|
||||||
let noise_se = host
|
|
||||||
.get_local_s_keypair()
|
|
||||||
.agree(&bob_e_public)
|
|
||||||
.ok_or(Error::FailedAuthentication)?;
|
.ok_or(Error::FailedAuthentication)?;
|
||||||
|
let noise_ee = offer.alice_e_keypair.agree(&bob_e_public).ok_or(Error::FailedAuthentication)?;
|
||||||
|
let noise_se = host.get_local_s_keypair().agree(&bob_e_public).ok_or(Error::FailedAuthentication)?;
|
||||||
|
|
||||||
let noise_ik_key = Secret(hmac_sha512(
|
let noise_ik_key = Secret(hmac_sha512(
|
||||||
session.psk.as_bytes(),
|
session.psk.as_bytes(),
|
||||||
|
@ -1077,17 +1097,17 @@ impl<Layer: ApplicationLayer> ReceiveContext<Layer> {
|
||||||
|
|
||||||
// Alice has now completed Noise_IK with NIST P-384 and verified with GCM auth, but now for hybrid...
|
// Alice has now completed Noise_IK with NIST P-384 and verified with GCM auth, but now for hybrid...
|
||||||
|
|
||||||
let (offer_id, bob_session_id, _, _, bob_hk_public_raw, bob_ratchet_key_id) = parse_dec_key_offer_after_header(
|
let (offer_id, bob_session_id, _, _, bob_hk_public_raw, bob_ratchet_key_id) =
|
||||||
&kex_packet[plaintext_end..kex_packet_len],
|
parse_dec_key_offer_after_header(&kex_packet[plaintext_end..kex_packet_len], packet_type)?;
|
||||||
packet_type,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
if !offer.id.eq(offer_id) {
|
if !offer.id.eq(offer_id) {
|
||||||
return Ok(ReceiveResult::Ignored);
|
return Ok(ReceiveResult::Ignored);
|
||||||
}
|
}
|
||||||
|
|
||||||
let hybrid_kk = if JEDI && bob_hk_public_raw.len() > 0 && offer.alice_hk_keypair.is_some() {
|
let hybrid_kk = if JEDI && bob_hk_public_raw.len() > 0 && offer.alice_hk_keypair.is_some() {
|
||||||
if let Ok(hybrid_kk) = pqc_kyber::decapsulate(bob_hk_public_raw, &offer.alice_hk_keypair.as_ref().unwrap().secret) {
|
if let Ok(hybrid_kk) =
|
||||||
|
pqc_kyber::decapsulate(bob_hk_public_raw, &offer.alice_hk_keypair.as_ref().unwrap().secret)
|
||||||
|
{
|
||||||
Some(Secret(hybrid_kk))
|
Some(Secret(hybrid_kk))
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::FailedAuthentication);
|
return Err(Error::FailedAuthentication);
|
||||||
|
@ -1121,7 +1141,14 @@ impl<Layer: ApplicationLayer> ReceiveContext<Layer> {
|
||||||
// Alice has now completed and validated the full hybrid exchange.
|
// Alice has now completed and validated the full hybrid exchange.
|
||||||
|
|
||||||
let counter = session.send_counter.next();
|
let counter = session.send_counter.next();
|
||||||
let session_key = SessionKey::new(session_key, Role::Alice, current_time, counter, ratchet_count + 1, hybrid_kk.is_some());
|
let session_key = SessionKey::new(
|
||||||
|
session_key,
|
||||||
|
Role::Alice,
|
||||||
|
current_time,
|
||||||
|
counter,
|
||||||
|
ratchet_count + 1,
|
||||||
|
hybrid_kk.is_some(),
|
||||||
|
);
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////
|
||||||
// packet encoding for post-noise session start ack
|
// packet encoding for post-noise session start ack
|
||||||
|
@ -1167,7 +1194,6 @@ impl<Layer: ApplicationLayer> ReceiveContext<Layer> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Create and send an ephemeral offer, returning the EphemeralOffer part that must be saved.
|
/// Create and send an ephemeral offer, returning the EphemeralOffer part that must be saved.
|
||||||
fn send_ephemeral_offer<SendFunction: FnMut(&mut [u8])>(
|
fn send_ephemeral_offer<SendFunction: FnMut(&mut [u8])>(
|
||||||
send: &mut SendFunction,
|
send: &mut SendFunction,
|
||||||
|
@ -1183,7 +1209,7 @@ fn send_ephemeral_offer<SendFunction: FnMut(&mut [u8])>(
|
||||||
header_check_cipher: Option<&Aes>, // None to use one based on the recipient's public key for initial contact
|
header_check_cipher: Option<&Aes>, // None to use one based on the recipient's public key for initial contact
|
||||||
mtu: usize,
|
mtu: usize,
|
||||||
current_time: i64,
|
current_time: i64,
|
||||||
ret_ephemeral_offer: &mut Option<EphemeralOffer>//We want to prevent copying the EphemeralOffer up the stack because it's very big. ret_ephemeral_offer will be overwritten with the returned EphemeralOffer when the call completes.
|
ret_ephemeral_offer: &mut Option<EphemeralOffer>, //We want to prevent copying the EphemeralOffer up the stack because it's very big. ret_ephemeral_offer will be overwritten with the returned EphemeralOffer when the call completes.
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
// Generate a NIST P-384 pair.
|
// Generate a NIST P-384 pair.
|
||||||
let alice_e_keypair = P384KeyPair::generate();
|
let alice_e_keypair = P384KeyPair::generate();
|
||||||
|
@ -1208,7 +1234,6 @@ fn send_ephemeral_offer<SendFunction: FnMut(&mut [u8])>(
|
||||||
// Random ephemeral offer ID
|
// Random ephemeral offer ID
|
||||||
let id: [u8; 16] = random::get_bytes_secure();
|
let id: [u8; 16] = random::get_bytes_secure();
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////
|
||||||
// packet encoding for noise initial key offer and for noise rekeying
|
// packet encoding for noise initial key offer and for noise rekeying
|
||||||
// -> e, es, s, ss
|
// -> e, es, s, ss
|
||||||
|
@ -1244,7 +1269,6 @@ fn send_ephemeral_offer<SendFunction: FnMut(&mut [u8])>(
|
||||||
}
|
}
|
||||||
let payload_end = idx;
|
let payload_end = idx;
|
||||||
|
|
||||||
|
|
||||||
// Create ephemeral agreement secret.
|
// Create ephemeral agreement secret.
|
||||||
let es_key = Secret(hmac_sha512(
|
let es_key = Secret(hmac_sha512(
|
||||||
&hmac_sha512(&INITIAL_KEY, alice_e_keypair.public_key_bytes()),
|
&hmac_sha512(&INITIAL_KEY, alice_e_keypair.public_key_bytes()),
|
||||||
|
@ -1252,7 +1276,14 @@ fn send_ephemeral_offer<SendFunction: FnMut(&mut [u8])>(
|
||||||
));
|
));
|
||||||
|
|
||||||
let bob_session_id = bob_session_id.unwrap_or(SessionId::NIL);
|
let bob_session_id = bob_session_id.unwrap_or(SessionId::NIL);
|
||||||
create_packet_header(&mut packet_buf, payload_end, mtu, PACKET_TYPE_INITIAL_KEY_OFFER, bob_session_id, counter)?;
|
create_packet_header(
|
||||||
|
&mut packet_buf,
|
||||||
|
payload_end,
|
||||||
|
mtu,
|
||||||
|
PACKET_TYPE_INITIAL_KEY_OFFER,
|
||||||
|
bob_session_id,
|
||||||
|
counter,
|
||||||
|
)?;
|
||||||
|
|
||||||
let canonical_header = CanonicalHeader::make(bob_session_id, PACKET_TYPE_INITIAL_KEY_OFFER, counter.to_u32());
|
let canonical_header = CanonicalHeader::make(bob_session_id, PACKET_TYPE_INITIAL_KEY_OFFER, counter.to_u32());
|
||||||
|
|
||||||
|
@ -1284,7 +1315,11 @@ fn send_ephemeral_offer<SendFunction: FnMut(&mut [u8])>(
|
||||||
let hmac1_end = idx;
|
let hmac1_end = idx;
|
||||||
|
|
||||||
// Add secondary HMAC to verify that the caller knows the recipient's full static public identity.
|
// Add secondary HMAC to verify that the caller knows the recipient's full static public identity.
|
||||||
let hmac2 = hmac_sha384_2(bob_s_public_blob_hash, canonical_header.as_bytes(), &packet_buf[HEADER_SIZE..hmac1_end]);
|
let hmac2 = hmac_sha384_2(
|
||||||
|
bob_s_public_blob_hash,
|
||||||
|
canonical_header.as_bytes(),
|
||||||
|
&packet_buf[HEADER_SIZE..hmac1_end],
|
||||||
|
);
|
||||||
idx = safe_write_all(&mut packet_buf, idx, &hmac2)?;
|
idx = safe_write_all(&mut packet_buf, idx, &hmac2)?;
|
||||||
let packet_end = idx;
|
let packet_end = idx;
|
||||||
|
|
||||||
|
@ -1342,7 +1377,7 @@ fn create_packet_header(
|
||||||
memory::store_raw((counter.to_u32() as u64).to_le(), header);
|
memory::store_raw((counter.to_u32() as u64).to_le(), header);
|
||||||
memory::store_raw(
|
memory::store_raw(
|
||||||
(u64::from(recipient_session_id) | (packet_type as u64).wrapping_shl(48) | ((fragment_count - 1) as u64).wrapping_shl(52))
|
(u64::from(recipient_session_id) | (packet_type as u64).wrapping_shl(48) | ((fragment_count - 1) as u64).wrapping_shl(52))
|
||||||
.to_le(),
|
.to_le(),
|
||||||
&mut header[8..],
|
&mut header[8..],
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1435,26 +1470,25 @@ fn parse_dec_key_offer_after_header(
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
Ok((
|
Ok((
|
||||||
offer_id,//always 16 bytes
|
offer_id, //always 16 bytes
|
||||||
alice_session_id,
|
alice_session_id,
|
||||||
alice_s_public_blob,
|
alice_s_public_blob,
|
||||||
alice_metadata,
|
alice_metadata,
|
||||||
alice_hk_public_raw,
|
alice_hk_public_raw,
|
||||||
alice_ratchet_key_fingerprint,//always 16 bytes
|
alice_ratchet_key_fingerprint, //always 16 bytes
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl KeyLifetime {
|
impl KeyLifetime {
|
||||||
fn new(current_counter: CounterValue, current_time: i64) -> Self {
|
fn new(current_counter: CounterValue, current_time: i64) -> Self {
|
||||||
Self {
|
Self {
|
||||||
rekey_at_or_after_counter: current_counter.0
|
rekey_at_or_after_counter: current_counter.0
|
||||||
+ REKEY_AFTER_USES
|
+ REKEY_AFTER_USES
|
||||||
+ (random::next_u32_secure() % REKEY_AFTER_USES_MAX_JITTER) as u64,
|
+ (random::next_u32_secure() % REKEY_AFTER_USES_MAX_JITTER) as u64,
|
||||||
hard_expire_at_counter: current_counter.0 + EXPIRE_AFTER_USES,
|
hard_expire_at_counter: current_counter.0 + EXPIRE_AFTER_USES,
|
||||||
rekey_at_or_after_timestamp: current_time
|
rekey_at_or_after_timestamp: current_time
|
||||||
+ REKEY_AFTER_TIME_MS
|
+ REKEY_AFTER_TIME_MS
|
||||||
+ (random::next_u32_secure() % REKEY_AFTER_TIME_MS_MAX_JITTER) as i64,
|
+ (random::next_u32_secure() % REKEY_AFTER_TIME_MS_MAX_JITTER) as i64,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1496,14 +1530,12 @@ impl SessionKey {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn get_send_cipher(&self, counter: CounterValue) -> Result<Box<AesGcm>, Error> {
|
fn get_send_cipher(&self, counter: CounterValue) -> Result<Box<AesGcm>, Error> {
|
||||||
if !self.lifetime.expired(counter) {
|
if !self.lifetime.expired(counter) {
|
||||||
Ok(
|
Ok(self
|
||||||
self
|
|
||||||
.send_cipher_pool
|
.send_cipher_pool
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.pop()
|
.pop()
|
||||||
.unwrap_or_else(|| Box::new(AesGcm::new(self.send_key.as_bytes(), true)))
|
.unwrap_or_else(|| Box::new(AesGcm::new(self.send_key.as_bytes(), true))))
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
// Not only do we return an error, but we also destroy the key.
|
// Not only do we return an error, but we also destroy the key.
|
||||||
let mut scp = self.send_cipher_pool.lock().unwrap();
|
let mut scp = self.send_cipher_pool.lock().unwrap();
|
||||||
|
@ -1522,10 +1554,10 @@ impl SessionKey {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn get_receive_cipher(&self) -> Box<AesGcm> {
|
fn get_receive_cipher(&self) -> Box<AesGcm> {
|
||||||
self.receive_cipher_pool
|
self.receive_cipher_pool
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.pop()
|
.pop()
|
||||||
.unwrap_or_else(|| Box::new(AesGcm::new(self.receive_key.as_bytes(), false)))
|
.unwrap_or_else(|| Box::new(AesGcm::new(self.receive_key.as_bytes(), false)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
Loading…
Add table
Reference in a new issue