mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-07-27 04:32:51 +02:00
docs, comments, readable code
This commit is contained in:
parent
1d23f40226
commit
7522282c2e
5 changed files with 208 additions and 131 deletions
|
@ -426,7 +426,7 @@ impl Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
if member_changed {
|
if member_changed {
|
||||||
self.database.save_member(member).await?;
|
self.database.save_member(member, false).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((authorization_result, network_config, revocations))
|
Ok((authorization_result, network_config, revocations))
|
||||||
|
|
|
@ -23,11 +23,11 @@ pub enum Change {
|
||||||
pub trait Database: Sync + Send + NodeStorage + 'static {
|
pub trait Database: Sync + Send + NodeStorage + 'static {
|
||||||
async fn list_networks(&self) -> Result<Vec<NetworkId>, Box<dyn Error + Send + Sync>>;
|
async fn list_networks(&self) -> Result<Vec<NetworkId>, Box<dyn Error + Send + Sync>>;
|
||||||
async fn get_network(&self, id: NetworkId) -> Result<Option<Network>, Box<dyn Error + Send + Sync>>;
|
async fn get_network(&self, id: NetworkId) -> Result<Option<Network>, Box<dyn Error + Send + Sync>>;
|
||||||
async fn save_network(&self, obj: Network) -> Result<(), Box<dyn Error + Send + Sync>>;
|
async fn save_network(&self, obj: Network, generate_change_notification: bool) -> Result<(), Box<dyn Error + Send + Sync>>;
|
||||||
|
|
||||||
async fn list_members(&self, network_id: NetworkId) -> Result<Vec<Address>, Box<dyn Error + Send + Sync>>;
|
async fn list_members(&self, network_id: NetworkId) -> Result<Vec<Address>, Box<dyn Error + Send + Sync>>;
|
||||||
async fn get_member(&self, network_id: NetworkId, node_id: Address) -> Result<Option<Member>, Box<dyn Error + Send + Sync>>;
|
async fn get_member(&self, network_id: NetworkId, node_id: Address) -> Result<Option<Member>, Box<dyn Error + Send + Sync>>;
|
||||||
async fn save_member(&self, obj: Member) -> Result<(), Box<dyn Error + Send + Sync>>;
|
async fn save_member(&self, obj: Member, generate_change_notification: bool) -> Result<(), Box<dyn Error + Send + Sync>>;
|
||||||
|
|
||||||
/// Get a receiver that can be used to receive changes made to networks and members, if supported.
|
/// Get a receiver that can be used to receive changes made to networks and members, if supported.
|
||||||
///
|
///
|
||||||
|
|
|
@ -30,6 +30,9 @@ const EVENT_HANDLER_TASK_TIMEOUT: Duration = Duration::from_secs(5);
|
||||||
/// A cache is maintained that contains the actual objects. When an object is live edited,
|
/// A cache is maintained that contains the actual objects. When an object is live edited,
|
||||||
/// once it successfully reads and loads it is merged with the cached object and saved to
|
/// once it successfully reads and loads it is merged with the cached object and saved to
|
||||||
/// the cache. The cache will also contain any ephemeral data, generated data, etc.
|
/// the cache. The cache will also contain any ephemeral data, generated data, etc.
|
||||||
|
///
|
||||||
|
/// The file format is YAML instead of JSON for better human friendliness and the layout
|
||||||
|
/// is different from V1 so it'll need a converter to use with V1 FileDb controller data.
|
||||||
pub struct FileDatabase {
|
pub struct FileDatabase {
|
||||||
base_path: PathBuf,
|
base_path: PathBuf,
|
||||||
controller_address: AtomicU64,
|
controller_address: AtomicU64,
|
||||||
|
@ -332,14 +335,17 @@ impl Database for FileDatabase {
|
||||||
let network_id_should_be = network.id.change_network_controller(controller_address);
|
let network_id_should_be = network.id.change_network_controller(controller_address);
|
||||||
if network.id != network_id_should_be {
|
if network.id != network_id_should_be {
|
||||||
network.id = network_id_should_be;
|
network.id = network_id_should_be;
|
||||||
let _ = self.save_network(network.clone()).await?;
|
let _ = self.save_network(network.clone(), false).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(network)
|
Ok(network)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn save_network(&self, obj: Network) -> Result<(), Box<dyn Error + Send + Sync>> {
|
async fn save_network(&self, obj: Network, generate_change_notification: bool) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||||
|
if !generate_change_notification {
|
||||||
|
let _ = self.cache.on_network_updated(obj.clone());
|
||||||
|
}
|
||||||
let base_network_path = self.network_path(obj.id);
|
let base_network_path = self.network_path(obj.id);
|
||||||
let _ = fs::create_dir_all(base_network_path.parent().unwrap()).await;
|
let _ = fs::create_dir_all(base_network_path.parent().unwrap()).await;
|
||||||
let _ = fs::write(base_network_path, serde_yaml::to_string(&obj)?.as_bytes()).await?;
|
let _ = fs::write(base_network_path, serde_yaml::to_string(&obj)?.as_bytes()).await?;
|
||||||
|
@ -374,13 +380,16 @@ impl Database for FileDatabase {
|
||||||
if member.network_id != network_id {
|
if member.network_id != network_id {
|
||||||
// Also auto-update member network IDs, see get_network().
|
// Also auto-update member network IDs, see get_network().
|
||||||
member.network_id = network_id;
|
member.network_id = network_id;
|
||||||
self.save_member(member.clone()).await?;
|
self.save_member(member.clone(), false).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(member)
|
Ok(member)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn save_member(&self, obj: Member) -> Result<(), Box<dyn Error + Send + Sync>> {
|
async fn save_member(&self, obj: Member, generate_change_notification: bool) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||||
|
if !generate_change_notification {
|
||||||
|
let _ = self.cache.on_member_updated(obj.clone());
|
||||||
|
}
|
||||||
let base_member_path = self.member_path(obj.network_id, obj.node_id);
|
let base_member_path = self.member_path(obj.network_id, obj.node_id);
|
||||||
let _ = fs::create_dir_all(base_member_path.parent().unwrap()).await;
|
let _ = fs::create_dir_all(base_member_path.parent().unwrap()).await;
|
||||||
let _ = fs::write(base_member_path, serde_yaml::to_string(&obj)?.as_bytes()).await?;
|
let _ = fs::write(base_member_path, serde_yaml::to_string(&obj)?.as_bytes()).await?;
|
||||||
|
@ -401,6 +410,7 @@ impl Database for FileDatabase {
|
||||||
mod tests {
|
mod tests {
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -420,11 +430,15 @@ mod tests {
|
||||||
db.save_node_identity(&controller_id);
|
db.save_node_identity(&controller_id);
|
||||||
assert!(db.load_node_identity().is_some());
|
assert!(db.load_node_identity().is_some());
|
||||||
|
|
||||||
|
let change_count = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
let db2 = db.clone();
|
let db2 = db.clone();
|
||||||
|
let change_count2 = change_count.clone();
|
||||||
tokio_runtime.spawn(async move {
|
tokio_runtime.spawn(async move {
|
||||||
let mut change_receiver = db2.changes().await.unwrap();
|
let mut change_receiver = db2.changes().await.unwrap();
|
||||||
loop {
|
loop {
|
||||||
if let Ok(change) = change_receiver.recv().await {
|
if let Ok(change) = change_receiver.recv().await {
|
||||||
|
change_count2.fetch_add(1, Ordering::SeqCst);
|
||||||
//println!("[FileDatabase] {:#?}", change);
|
//println!("[FileDatabase] {:#?}", change);
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
|
@ -433,14 +447,16 @@ mod tests {
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut test_network = Network::new(network_id);
|
let mut test_network = Network::new(network_id);
|
||||||
db.save_network(test_network.clone()).await.expect("network save error");
|
db.save_network(test_network.clone(), true).await.expect("network save error");
|
||||||
|
|
||||||
let mut test_member = Member::new_without_identity(node_id, network_id);
|
let mut test_member = Member::new_without_identity(node_id, network_id);
|
||||||
for x in 0..3 {
|
for x in 0..3 {
|
||||||
test_member.name = x.to_string();
|
test_member.name = x.to_string();
|
||||||
db.save_member(test_member.clone()).await.expect("member save error");
|
db.save_member(test_member.clone(), true).await.expect("member save error");
|
||||||
|
|
||||||
|
zerotier_utils::tokio::task::yield_now().await;
|
||||||
sleep(Duration::from_millis(100)).await;
|
sleep(Duration::from_millis(100)).await;
|
||||||
|
zerotier_utils::tokio::task::yield_now().await;
|
||||||
|
|
||||||
let test_member2 = db.get_member(network_id, node_id).await.unwrap().unwrap();
|
let test_member2 = db.get_member(network_id, node_id).await.unwrap().unwrap();
|
||||||
assert!(test_member == test_member2);
|
assert!(test_member == test_member2);
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
ZeroTier Secure Socket Protocol
|
ZeroTier Secure Socket Protocol
|
||||||
======
|
======
|
||||||
|
|
||||||
|
**NOTE: this protocol and code have not yet been formally audited and should not be used in anything production.**
|
||||||
|
|
||||||
ZSSP (ZeroTier Secure Socket Protocol) is an implementation of the Noise_IK pattern using FIPS/NIST compliant primitives. After Noise_IK negotiation is complete ZSSP also adds key ratcheting and optional (enabled by default) support for quantum data forward secrecy with Kyber1024.
|
ZSSP (ZeroTier Secure Socket Protocol) is an implementation of the Noise_IK pattern using FIPS/NIST compliant primitives. After Noise_IK negotiation is complete ZSSP also adds key ratcheting and optional (enabled by default) support for quantum data forward secrecy with Kyber1024.
|
||||||
|
|
||||||
It's general purpose and could be used with any system but contains a few specific design choices to make it optimal for ZeroTier and easy to distinguish from legacy ZeroTier V1 traffic for backward compatibility.
|
It's general purpose and could be used with any system but contains a few specific design choices to make it optimal for ZeroTier and easy to distinguish from legacy ZeroTier V1 traffic for backward compatibility.
|
||||||
|
|
|
@ -20,10 +20,10 @@ use zerotier_utils::ringbuffermap::RingBufferMap;
|
||||||
use zerotier_utils::unlikely_branch;
|
use zerotier_utils::unlikely_branch;
|
||||||
use zerotier_utils::varint;
|
use zerotier_utils::varint;
|
||||||
|
|
||||||
/// Minimum size of a valid packet.
|
/// Minimum size of a valid physical packet.
|
||||||
pub const MIN_PACKET_SIZE: usize = HEADER_SIZE + AES_GCM_TAG_SIZE;
|
pub const MIN_PACKET_SIZE: usize = HEADER_SIZE + AES_GCM_TAG_SIZE;
|
||||||
|
|
||||||
/// Minimum wire MTU for ZSSP to function normally.
|
/// Minimum physical MTU for ZSSP to function.
|
||||||
pub const MIN_TRANSPORT_MTU: usize = 1280;
|
pub const MIN_TRANSPORT_MTU: usize = 1280;
|
||||||
|
|
||||||
/// Minimum recommended interval between calls to service() on each session, in milliseconds.
|
/// Minimum recommended interval between calls to service() on each session, in milliseconds.
|
||||||
|
@ -222,6 +222,10 @@ pub enum ReceiveResult<'a, H: Host> {
|
||||||
pub struct SessionId(u64);
|
pub struct SessionId(u64);
|
||||||
|
|
||||||
impl SessionId {
|
impl SessionId {
|
||||||
|
/// The nil session ID used in messages initiating a new session.
|
||||||
|
///
|
||||||
|
/// This is all 1's so that ZeroTier can easily tell the difference between ZSSP init packets
|
||||||
|
/// and ZeroTier V1 packets.
|
||||||
pub const NIL: SessionId = SessionId(0xffffffffffff);
|
pub const NIL: SessionId = SessionId(0xffffffffffff);
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -242,7 +246,7 @@ impl SessionId {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new_random() -> Self {
|
pub fn new_random() -> Self {
|
||||||
Self(random::next_u64_secure() % (Self::NIL.0 - 1))
|
Self(random::next_u64_secure() % Self::NIL.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -331,13 +335,16 @@ pub trait Host: Sized {
|
||||||
|
|
||||||
/// ZSSP bi-directional packet transport channel.
|
/// ZSSP bi-directional packet transport channel.
|
||||||
pub struct Session<H: Host> {
|
pub struct Session<H: Host> {
|
||||||
|
/// This side's session ID (unique on this side)
|
||||||
pub id: SessionId,
|
pub id: SessionId,
|
||||||
|
|
||||||
|
/// An arbitrary object associated with session (type defined in Host trait)
|
||||||
pub associated_object: H::AssociatedObject,
|
pub associated_object: H::AssociatedObject,
|
||||||
|
|
||||||
send_counter: Counter,
|
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
|
||||||
ss: Secret<48>, // NIST P-384 raw ECDH key agreement with peer
|
ss: Secret<48>, // Static raw shared ECDH NIST P-384 key
|
||||||
header_check_cipher: Aes, // Cipher used for fast 32-bit header MAC
|
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_hash: [u8; 48], // SHA384(remote static public key blob)
|
remote_s_public_hash: [u8; 48], // SHA384(remote static public key blob)
|
||||||
remote_s_public_p384: [u8; P384_PUBLIC_KEY_SIZE], // Remote NIST P-384 static public key
|
remote_s_public_p384: [u8; P384_PUBLIC_KEY_SIZE], // Remote NIST P-384 static public key
|
||||||
|
@ -345,11 +352,11 @@ pub struct Session<H: Host> {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SessionMutableState {
|
struct SessionMutableState {
|
||||||
remote_session_id: Option<SessionId>,
|
remote_session_id: Option<SessionId>, // The other side's 48-bit session ID
|
||||||
keys: [Option<SessionKey>; KEY_HISTORY_SIZE],
|
keys: [Option<SessionKey>; KEY_HISTORY_SIZE], // Buffers to store current, next, and last active key
|
||||||
key_ptr: usize,
|
key_ptr: usize, // Pointer used for keys[] circular buffer
|
||||||
offer: Option<Box<EphemeralOffer>>,
|
offer: Option<Box<EphemeralOffer>>, // Most recent ephemeral offer sent to remote
|
||||||
last_remote_offer: i64,
|
last_remote_offer: i64, // Time of most recent ephemeral offer (ms)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<H: Host> Session<H> {
|
impl<H: Host> Session<H> {
|
||||||
|
@ -380,7 +387,8 @@ impl<H: Host> Session<H> {
|
||||||
let remote_s_public_hash = SHA384::hash(remote_s_public);
|
let remote_s_public_hash = SHA384::hash(remote_s_public);
|
||||||
let outgoing_init_header_check_cipher =
|
let outgoing_init_header_check_cipher =
|
||||||
Aes::new(kbkdf512(&remote_s_public_hash, KBKDF_KEY_USAGE_LABEL_HEADER_CHECK).first_n::<16>());
|
Aes::new(kbkdf512(&remote_s_public_hash, KBKDF_KEY_USAGE_LABEL_HEADER_CHECK).first_n::<16>());
|
||||||
if let Ok(offer) = create_initial_offer(
|
|
||||||
|
if let Ok(offer) = send_ephemeral_offer(
|
||||||
&mut send,
|
&mut send,
|
||||||
send_counter.next(),
|
send_counter.next(),
|
||||||
local_session_id,
|
local_session_id,
|
||||||
|
@ -421,8 +429,9 @@ impl<H: Host> Session<H> {
|
||||||
|
|
||||||
/// Send data over the session.
|
/// Send data over the session.
|
||||||
///
|
///
|
||||||
/// * `mtu_buffer` - A writable work buffer whose size must be equal to the wire MTU
|
/// * `mtu_buffer` - A writable work buffer whose size must be equal to the physical MTU
|
||||||
/// * `data` - Data to send
|
/// * `data` - Data to send
|
||||||
|
#[inline]
|
||||||
pub fn send<SendFunction: FnMut(&mut [u8])>(
|
pub fn send<SendFunction: FnMut(&mut [u8])>(
|
||||||
&self,
|
&self,
|
||||||
mut send: SendFunction,
|
mut send: SendFunction,
|
||||||
|
@ -433,9 +442,13 @@ impl<H: Host> Session<H> {
|
||||||
let state = self.state.read().unwrap();
|
let state = self.state.read().unwrap();
|
||||||
if let Some(remote_session_id) = state.remote_session_id {
|
if let Some(remote_session_id) = state.remote_session_id {
|
||||||
if let Some(key) = state.keys[state.key_ptr].as_ref() {
|
if let Some(key) = state.keys[state.key_ptr].as_ref() {
|
||||||
|
// Total size of the armored packet we are going to send (may end up being fragmented)
|
||||||
let mut packet_len = data.len() + HEADER_SIZE + AES_GCM_TAG_SIZE;
|
let mut packet_len = data.len() + HEADER_SIZE + AES_GCM_TAG_SIZE;
|
||||||
|
|
||||||
|
// This outgoing packet's nonce counter value.
|
||||||
let counter = self.send_counter.next();
|
let counter = self.send_counter.next();
|
||||||
|
|
||||||
|
// Create initial header for first fragment of packet and place in first HEADER_SIZE bytes of buffer.
|
||||||
create_packet_header(
|
create_packet_header(
|
||||||
mtu_buffer,
|
mtu_buffer,
|
||||||
packet_len,
|
packet_len,
|
||||||
|
@ -445,13 +458,12 @@ impl<H: Host> Session<H> {
|
||||||
counter,
|
counter,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
// Get an initialized AES-GCM cipher and re-initialize with a 96-bit IV built from remote session ID,
|
||||||
|
// packet type, and counter.
|
||||||
let mut c = key.get_send_cipher(counter)?;
|
let mut c = key.get_send_cipher(counter)?;
|
||||||
c.init(memory::as_byte_array::<Pseudoheader, 12>(&Pseudoheader::make(
|
c.init(CanonicalHeader::make(remote_session_id, PACKET_TYPE_DATA, counter.to_u32()).as_bytes());
|
||||||
remote_session_id.into(),
|
|
||||||
PACKET_TYPE_DATA,
|
|
||||||
counter.to_u32(),
|
|
||||||
)));
|
|
||||||
|
|
||||||
|
// Send first N-1 fragments of N total fragments.
|
||||||
if packet_len > mtu_buffer.len() {
|
if packet_len > mtu_buffer.len() {
|
||||||
let mut header: [u8; 16] = mtu_buffer[..HEADER_SIZE].try_into().unwrap();
|
let mut header: [u8; 16] = mtu_buffer[..HEADER_SIZE].try_into().unwrap();
|
||||||
let fragment_data_mtu = mtu_buffer.len() - HEADER_SIZE;
|
let fragment_data_mtu = mtu_buffer.len() - HEADER_SIZE;
|
||||||
|
@ -461,7 +473,7 @@ impl<H: Host> Session<H> {
|
||||||
let fragment_size = fragment_data_size + HEADER_SIZE;
|
let fragment_size = fragment_data_size + HEADER_SIZE;
|
||||||
c.crypt(&data[..fragment_data_size], &mut mtu_buffer[HEADER_SIZE..fragment_size]);
|
c.crypt(&data[..fragment_data_size], &mut mtu_buffer[HEADER_SIZE..fragment_size]);
|
||||||
data = &data[fragment_data_size..];
|
data = &data[fragment_data_size..];
|
||||||
set_header_mac(mtu_buffer, &self.header_check_cipher);
|
set_header_check_code(mtu_buffer, &self.header_check_cipher);
|
||||||
send(&mut mtu_buffer[..fragment_size]);
|
send(&mut mtu_buffer[..fragment_size]);
|
||||||
|
|
||||||
debug_assert!(header[15].wrapping_shr(2) < 63);
|
debug_assert!(header[15].wrapping_shr(2) < 63);
|
||||||
|
@ -475,17 +487,22 @@ impl<H: Host> Session<H> {
|
||||||
packet_len = data.len() + HEADER_SIZE + AES_GCM_TAG_SIZE;
|
packet_len = data.len() + HEADER_SIZE + AES_GCM_TAG_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send final fragment (or only fragment if no fragmentation was needed)
|
||||||
let gcm_tag_idx = data.len() + HEADER_SIZE;
|
let gcm_tag_idx = data.len() + HEADER_SIZE;
|
||||||
c.crypt(data, &mut mtu_buffer[HEADER_SIZE..gcm_tag_idx]);
|
c.crypt(data, &mut mtu_buffer[HEADER_SIZE..gcm_tag_idx]);
|
||||||
mtu_buffer[gcm_tag_idx..packet_len].copy_from_slice(&c.finish_encrypt());
|
mtu_buffer[gcm_tag_idx..packet_len].copy_from_slice(&c.finish_encrypt());
|
||||||
|
set_header_check_code(mtu_buffer, &self.header_check_cipher);
|
||||||
set_header_mac(mtu_buffer, &self.header_check_cipher);
|
|
||||||
send(&mut mtu_buffer[..packet_len]);
|
send(&mut mtu_buffer[..packet_len]);
|
||||||
|
|
||||||
|
// Check reusable AES-GCM instance back into pool.
|
||||||
key.return_send_cipher(c);
|
key.return_send_cipher(c);
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
} else {
|
||||||
|
unlikely_branch();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
unlikely_branch();
|
||||||
}
|
}
|
||||||
return Err(Error::SessionNotEstablished);
|
return Err(Error::SessionNotEstablished);
|
||||||
}
|
}
|
||||||
|
@ -503,7 +520,7 @@ impl<H: Host> Session<H> {
|
||||||
pub fn security_info(&self) -> Option<([u8; 16], i64, u64, bool)> {
|
pub fn security_info(&self) -> Option<([u8; 16], i64, u64, bool)> {
|
||||||
let state = self.state.read().unwrap();
|
let state = self.state.read().unwrap();
|
||||||
if let Some(key) = state.keys[state.key_ptr].as_ref() {
|
if let Some(key) = state.keys[state.key_ptr].as_ref() {
|
||||||
Some((key.fingerprint, key.establish_time, key.ratchet_count, key.jedi))
|
Some((key.secret_fingerprint, key.establish_time, key.ratchet_count, key.jedi))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -528,7 +545,7 @@ impl<H: Host> Session<H> {
|
||||||
if (force_rekey
|
if (force_rekey
|
||||||
|| state.keys[state.key_ptr]
|
|| state.keys[state.key_ptr]
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or(true, |key| key.lifetime.should_rekey(self.send_counter.current(), current_time)))
|
.map_or(true, |key| key.lifetime.should_rekey(self.send_counter.previous(), current_time)))
|
||||||
&& state
|
&& state
|
||||||
.offer
|
.offer
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -536,7 +553,7 @@ impl<H: Host> Session<H> {
|
||||||
{
|
{
|
||||||
if let Some(remote_s_public_p384) = P384PublicKey::from_bytes(&self.remote_s_public_p384) {
|
if let Some(remote_s_public_p384) = P384PublicKey::from_bytes(&self.remote_s_public_p384) {
|
||||||
let mut tmp_header_check_cipher = None;
|
let mut tmp_header_check_cipher = None;
|
||||||
if let Ok(offer) = create_initial_offer(
|
if let Ok(offer) = send_ephemeral_offer(
|
||||||
&mut send,
|
&mut send,
|
||||||
self.send_counter.next(),
|
self.send_counter.next(),
|
||||||
self.id,
|
self.id,
|
||||||
|
@ -602,13 +619,13 @@ impl<H: Host> ReceiveContext<H> {
|
||||||
let packet_type_fragment_info = u16::from_le(memory::load_raw(&incoming_packet[14..16]));
|
let packet_type_fragment_info = u16::from_le(memory::load_raw(&incoming_packet[14..16]));
|
||||||
let packet_type = (packet_type_fragment_info & 0x0f) as u8;
|
let packet_type = (packet_type_fragment_info & 0x0f) as u8;
|
||||||
let fragment_count = ((packet_type_fragment_info.wrapping_shr(4) + 1) as u8) & 63;
|
let fragment_count = ((packet_type_fragment_info.wrapping_shr(4) + 1) as u8) & 63;
|
||||||
let fragment_no = packet_type_fragment_info.wrapping_shr(10) as u8;
|
let fragment_no = packet_type_fragment_info.wrapping_shr(10) as u8; // & 63 not needed
|
||||||
|
|
||||||
if let Some(local_session_id) = SessionId::new_from_u64(u64::from_le(memory::load_raw(&incoming_packet[8..16])) & 0xffffffffffffu64)
|
if let Some(local_session_id) = SessionId::new_from_u64(u64::from_le(memory::load_raw(&incoming_packet[8..16])) & 0xffffffffffffu64)
|
||||||
{
|
{
|
||||||
if let Some(session) = host.session_lookup(local_session_id) {
|
if let Some(session) = host.session_lookup(local_session_id) {
|
||||||
if check_header_mac(incoming_packet, &session.header_check_cipher) {
|
if verify_header_check_code(incoming_packet, &session.header_check_cipher) {
|
||||||
let pseudoheader = Pseudoheader::make(u64::from(local_session_id), packet_type, counter);
|
let canonical_header = CanonicalHeader::make(local_session_id, packet_type, counter);
|
||||||
if fragment_count > 1 {
|
if fragment_count > 1 {
|
||||||
if fragment_count <= (MAX_FRAGMENTS as u8) && fragment_no < fragment_count {
|
if fragment_count <= (MAX_FRAGMENTS as u8) && fragment_no < fragment_count {
|
||||||
let mut defrag = session.defrag.lock().unwrap();
|
let mut defrag = session.defrag.lock().unwrap();
|
||||||
|
@ -620,7 +637,7 @@ impl<H: Host> ReceiveContext<H> {
|
||||||
remote_address,
|
remote_address,
|
||||||
&mut send,
|
&mut send,
|
||||||
data_buf,
|
data_buf,
|
||||||
memory::as_byte_array(&pseudoheader),
|
canonical_header.as_bytes(),
|
||||||
assembled_packet.as_ref(),
|
assembled_packet.as_ref(),
|
||||||
packet_type,
|
packet_type,
|
||||||
Some(session),
|
Some(session),
|
||||||
|
@ -638,7 +655,7 @@ impl<H: Host> ReceiveContext<H> {
|
||||||
remote_address,
|
remote_address,
|
||||||
&mut send,
|
&mut send,
|
||||||
data_buf,
|
data_buf,
|
||||||
memory::as_byte_array(&pseudoheader),
|
canonical_header.as_bytes(),
|
||||||
&[incoming_packet_buf],
|
&[incoming_packet_buf],
|
||||||
packet_type,
|
packet_type,
|
||||||
Some(session),
|
Some(session),
|
||||||
|
@ -655,9 +672,10 @@ impl<H: Host> ReceiveContext<H> {
|
||||||
return Err(Error::UnknownLocalSessionId(local_session_id));
|
return Err(Error::UnknownLocalSessionId(local_session_id));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
unlikely_branch();
|
unlikely_branch(); // we want data receive to be the priority branch, this is only occasionally used
|
||||||
if check_header_mac(incoming_packet, &self.incoming_init_header_check_cipher) {
|
|
||||||
let pseudoheader = Pseudoheader::make(SessionId::NIL.0, packet_type, counter);
|
if verify_header_check_code(incoming_packet, &self.incoming_init_header_check_cipher) {
|
||||||
|
let canonical_header = CanonicalHeader::make(SessionId::NIL, packet_type, counter);
|
||||||
if fragment_count > 1 {
|
if fragment_count > 1 {
|
||||||
let mut defrag = self.initial_offer_defrag.lock().unwrap();
|
let mut defrag = self.initial_offer_defrag.lock().unwrap();
|
||||||
let fragment_gather_array = defrag.get_or_create_mut(&counter, || GatherArray::new(fragment_count));
|
let fragment_gather_array = defrag.get_or_create_mut(&counter, || GatherArray::new(fragment_count));
|
||||||
|
@ -668,7 +686,7 @@ impl<H: Host> ReceiveContext<H> {
|
||||||
remote_address,
|
remote_address,
|
||||||
&mut send,
|
&mut send,
|
||||||
data_buf,
|
data_buf,
|
||||||
memory::as_byte_array(&pseudoheader),
|
canonical_header.as_bytes(),
|
||||||
assembled_packet.as_ref(),
|
assembled_packet.as_ref(),
|
||||||
packet_type,
|
packet_type,
|
||||||
None,
|
None,
|
||||||
|
@ -682,7 +700,7 @@ impl<H: Host> ReceiveContext<H> {
|
||||||
remote_address,
|
remote_address,
|
||||||
&mut send,
|
&mut send,
|
||||||
data_buf,
|
data_buf,
|
||||||
memory::as_byte_array(&pseudoheader),
|
canonical_header.as_bytes(),
|
||||||
&[incoming_packet_buf],
|
&[incoming_packet_buf],
|
||||||
packet_type,
|
packet_type,
|
||||||
None,
|
None,
|
||||||
|
@ -699,13 +717,15 @@ impl<H: Host> ReceiveContext<H> {
|
||||||
return Ok(ReceiveResult::Ok);
|
return Ok(ReceiveResult::Ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Called internally when all fragments of a packet are received.
|
||||||
|
/// Header check codes will already have been validated for each fragment.
|
||||||
fn receive_complete<'a, SendFunction: FnMut(&mut [u8])>(
|
fn receive_complete<'a, SendFunction: FnMut(&mut [u8])>(
|
||||||
&self,
|
&self,
|
||||||
host: &H,
|
host: &H,
|
||||||
remote_address: &H::RemoteAddress,
|
remote_address: &H::RemoteAddress,
|
||||||
send: &mut SendFunction,
|
send: &mut SendFunction,
|
||||||
data_buf: &'a mut [u8],
|
data_buf: &'a mut [u8],
|
||||||
pseudoheader: &[u8; 12],
|
canonical_header_bytes: &[u8; 12],
|
||||||
fragments: &[H::IncomingPacketBuffer],
|
fragments: &[H::IncomingPacketBuffer],
|
||||||
packet_type: u8,
|
packet_type: u8,
|
||||||
session: Option<H::SessionRef>,
|
session: Option<H::SessionRef>,
|
||||||
|
@ -714,8 +734,10 @@ impl<H: Host> ReceiveContext<H> {
|
||||||
) -> Result<ReceiveResult<'a, H>, Error> {
|
) -> Result<ReceiveResult<'a, H>, Error> {
|
||||||
debug_assert!(fragments.len() >= 1);
|
debug_assert!(fragments.len() >= 1);
|
||||||
|
|
||||||
|
// These just confirm that the first 'if' below does what it should do.
|
||||||
debug_assert_eq!(PACKET_TYPE_DATA, 0);
|
debug_assert_eq!(PACKET_TYPE_DATA, 0);
|
||||||
debug_assert_eq!(PACKET_TYPE_NOP, 1);
|
debug_assert_eq!(PACKET_TYPE_NOP, 1);
|
||||||
|
|
||||||
if packet_type <= PACKET_TYPE_NOP {
|
if packet_type <= PACKET_TYPE_NOP {
|
||||||
if let Some(session) = session {
|
if let Some(session) = session {
|
||||||
let state = session.state.read().unwrap();
|
let state = session.state.read().unwrap();
|
||||||
|
@ -729,7 +751,7 @@ impl<H: Host> ReceiveContext<H> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut c = key.get_receive_cipher();
|
let mut c = key.get_receive_cipher();
|
||||||
c.init(pseudoheader);
|
c.init(canonical_header_bytes);
|
||||||
|
|
||||||
let mut data_len = 0;
|
let mut data_len = 0;
|
||||||
|
|
||||||
|
@ -797,7 +819,9 @@ impl<H: Host> ReceiveContext<H> {
|
||||||
} else {
|
} else {
|
||||||
unlikely_branch();
|
unlikely_branch();
|
||||||
|
|
||||||
let mut incoming_packet_buf = [0_u8; 4096]; // big enough for key exchange packets
|
// To greatly simplify logic handling key exchange packets, assemble these first.
|
||||||
|
// This adds some extra memory copying but this is not the fast path.
|
||||||
|
let mut incoming_packet_buf = [0_u8; 4096];
|
||||||
let mut incoming_packet_len = 0;
|
let mut incoming_packet_len = 0;
|
||||||
for i in 0..fragments.len() {
|
for i in 0..fragments.len() {
|
||||||
let mut ff = fragments[i].as_ref();
|
let mut ff = fragments[i].as_ref();
|
||||||
|
@ -833,7 +857,7 @@ impl<H: Host> ReceiveContext<H> {
|
||||||
// Check that the sender knows this host's identity before doing anything else.
|
// Check that the sender knows this host's identity before doing anything else.
|
||||||
if !hmac_sha384_2(
|
if !hmac_sha384_2(
|
||||||
host.get_local_s_public_hash(),
|
host.get_local_s_public_hash(),
|
||||||
pseudoheader,
|
canonical_header_bytes,
|
||||||
&incoming_packet[HEADER_SIZE..hmac1_end],
|
&incoming_packet[HEADER_SIZE..hmac1_end],
|
||||||
)
|
)
|
||||||
.eq(&incoming_packet[hmac1_end..])
|
.eq(&incoming_packet[hmac1_end..])
|
||||||
|
@ -866,7 +890,7 @@ impl<H: Host> ReceiveContext<H> {
|
||||||
kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB).first_n::<32>(),
|
kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB).first_n::<32>(),
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
c.init(pseudoheader);
|
c.init(canonical_header_bytes);
|
||||||
c.crypt_in_place(&mut incoming_packet[(HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE)..payload_end]);
|
c.crypt_in_place(&mut incoming_packet[(HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE)..payload_end]);
|
||||||
if !c.finish_decrypt(&incoming_packet[payload_end..aes_gcm_tag_end]) {
|
if !c.finish_decrypt(&incoming_packet[payload_end..aes_gcm_tag_end]) {
|
||||||
return Err(Error::FailedAuthentication);
|
return Err(Error::FailedAuthentication);
|
||||||
|
@ -898,7 +922,7 @@ impl<H: Host> ReceiveContext<H> {
|
||||||
// just mixed into the key.
|
// just mixed into the key.
|
||||||
if !hmac_sha384_2(
|
if !hmac_sha384_2(
|
||||||
kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_HMAC).first_n::<48>(),
|
kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_HMAC).first_n::<48>(),
|
||||||
pseudoheader,
|
canonical_header_bytes,
|
||||||
&original_ciphertext[HEADER_SIZE..aes_gcm_tag_end],
|
&original_ciphertext[HEADER_SIZE..aes_gcm_tag_end],
|
||||||
)
|
)
|
||||||
.eq(&incoming_packet[aes_gcm_tag_end..hmac1_end])
|
.eq(&incoming_packet[aes_gcm_tag_end..hmac1_end])
|
||||||
|
@ -923,7 +947,7 @@ impl<H: Host> ReceiveContext<H> {
|
||||||
let state = session.state.read().unwrap();
|
let state = session.state.read().unwrap();
|
||||||
for k in state.keys.iter() {
|
for k in state.keys.iter() {
|
||||||
if let Some(k) = k.as_ref() {
|
if let Some(k) = k.as_ref() {
|
||||||
if key_fingerprint(k.ratchet_key.as_bytes())[..16].eq(alice_ratchet_key_fingerprint) {
|
if secret_fingerprint(k.ratchet_key.as_bytes())[..16].eq(alice_ratchet_key_fingerprint) {
|
||||||
ratchet_key = Some(k.ratchet_key.clone());
|
ratchet_key = Some(k.ratchet_key.clone());
|
||||||
ratchet_count = k.ratchet_count;
|
ratchet_count = k.ratchet_count;
|
||||||
break;
|
break;
|
||||||
|
@ -1044,8 +1068,8 @@ impl<H: Host> ReceiveContext<H> {
|
||||||
alice_session_id.into(),
|
alice_session_id.into(),
|
||||||
reply_counter,
|
reply_counter,
|
||||||
)?;
|
)?;
|
||||||
let reply_pseudoheader =
|
let reply_canonical_header =
|
||||||
Pseudoheader::make(alice_session_id.into(), PACKET_TYPE_KEY_COUNTER_OFFER, reply_counter.to_u32());
|
CanonicalHeader::make(alice_session_id.into(), PACKET_TYPE_KEY_COUNTER_OFFER, reply_counter.to_u32());
|
||||||
|
|
||||||
// Encrypt reply packet using final Noise_IK key BEFORE mixing hybrid or ratcheting, since the other side
|
// Encrypt reply packet using final Noise_IK key BEFORE mixing hybrid or ratcheting, since the other side
|
||||||
// must decrypt before doing these things.
|
// must decrypt before doing these things.
|
||||||
|
@ -1053,7 +1077,7 @@ impl<H: Host> ReceiveContext<H> {
|
||||||
kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE).first_n::<32>(),
|
kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE).first_n::<32>(),
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
c.init(memory::as_byte_array::<Pseudoheader, 12>(&reply_pseudoheader));
|
c.init(reply_canonical_header.as_bytes());
|
||||||
c.crypt_in_place(&mut reply_buf[(HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE)..reply_len]);
|
c.crypt_in_place(&mut reply_buf[(HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE)..reply_len]);
|
||||||
let c = c.finish_encrypt();
|
let c = c.finish_encrypt();
|
||||||
reply_buf[reply_len..(reply_len + AES_GCM_TAG_SIZE)].copy_from_slice(&c);
|
reply_buf[reply_len..(reply_len + AES_GCM_TAG_SIZE)].copy_from_slice(&c);
|
||||||
|
@ -1073,7 +1097,7 @@ impl<H: Host> ReceiveContext<H> {
|
||||||
// Kyber exchange, but you'd need a not-yet-existing quantum computer for that.
|
// Kyber exchange, but you'd need a not-yet-existing quantum computer for that.
|
||||||
let hmac = hmac_sha384_2(
|
let hmac = hmac_sha384_2(
|
||||||
kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_HMAC).first_n::<48>(),
|
kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_HMAC).first_n::<48>(),
|
||||||
memory::as_byte_array::<Pseudoheader, 12>(&reply_pseudoheader),
|
reply_canonical_header.as_bytes(),
|
||||||
&reply_buf[HEADER_SIZE..reply_len],
|
&reply_buf[HEADER_SIZE..reply_len],
|
||||||
);
|
);
|
||||||
reply_buf[reply_len..(reply_len + HMAC_SIZE)].copy_from_slice(&hmac);
|
reply_buf[reply_len..(reply_len + HMAC_SIZE)].copy_from_slice(&hmac);
|
||||||
|
@ -1131,7 +1155,7 @@ impl<H: Host> ReceiveContext<H> {
|
||||||
kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE).first_n::<32>(),
|
kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE).first_n::<32>(),
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
c.init(pseudoheader);
|
c.init(canonical_header_bytes);
|
||||||
c.crypt_in_place(&mut incoming_packet[(HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE)..payload_end]);
|
c.crypt_in_place(&mut incoming_packet[(HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE)..payload_end]);
|
||||||
if !c.finish_decrypt(&incoming_packet[payload_end..aes_gcm_tag_end]) {
|
if !c.finish_decrypt(&incoming_packet[payload_end..aes_gcm_tag_end]) {
|
||||||
return Err(Error::FailedAuthentication);
|
return Err(Error::FailedAuthentication);
|
||||||
|
@ -1167,7 +1191,7 @@ impl<H: Host> ReceiveContext<H> {
|
||||||
|
|
||||||
if !hmac_sha384_2(
|
if !hmac_sha384_2(
|
||||||
kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_HMAC).first_n::<48>(),
|
kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_HMAC).first_n::<48>(),
|
||||||
pseudoheader,
|
canonical_header_bytes,
|
||||||
&original_ciphertext[HEADER_SIZE..aes_gcm_tag_end],
|
&original_ciphertext[HEADER_SIZE..aes_gcm_tag_end],
|
||||||
)
|
)
|
||||||
.eq(&incoming_packet[aes_gcm_tag_end..incoming_packet.len()])
|
.eq(&incoming_packet[aes_gcm_tag_end..incoming_packet.len()])
|
||||||
|
@ -1191,15 +1215,11 @@ impl<H: Host> ReceiveContext<H> {
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut c = key.get_send_cipher(counter)?;
|
let mut c = key.get_send_cipher(counter)?;
|
||||||
c.init(memory::as_byte_array::<Pseudoheader, 12>(&Pseudoheader::make(
|
c.init(CanonicalHeader::make(bob_session_id.into(), PACKET_TYPE_NOP, counter.to_u32()).as_bytes());
|
||||||
bob_session_id.into(),
|
|
||||||
PACKET_TYPE_NOP,
|
|
||||||
counter.to_u32(),
|
|
||||||
)));
|
|
||||||
reply_buf[HEADER_SIZE..].copy_from_slice(&c.finish_encrypt());
|
reply_buf[HEADER_SIZE..].copy_from_slice(&c.finish_encrypt());
|
||||||
key.return_send_cipher(c);
|
key.return_send_cipher(c);
|
||||||
|
|
||||||
set_header_mac(&mut reply_buf, &session.header_check_cipher);
|
set_header_check_code(&mut reply_buf, &session.header_check_cipher);
|
||||||
send(&mut reply_buf);
|
send(&mut reply_buf);
|
||||||
|
|
||||||
drop(state);
|
drop(state);
|
||||||
|
@ -1224,19 +1244,24 @@ impl<H: Host> ReceiveContext<H> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Outgoing packet counter with strictly ordered atomic semantics.
|
||||||
struct Counter(AtomicU64);
|
struct Counter(AtomicU64);
|
||||||
|
|
||||||
impl Counter {
|
impl Counter {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
|
// Using a random value has no security implication. Zero would be fine. This just
|
||||||
|
// helps randomize packet contents a bit.
|
||||||
Self(AtomicU64::new(random::next_u32_secure() as u64))
|
Self(AtomicU64::new(random::next_u32_secure() as u64))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the value most recently used to send a packet.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn current(&self) -> CounterValue {
|
fn previous(&self) -> CounterValue {
|
||||||
CounterValue(self.0.load(Ordering::SeqCst))
|
CounterValue(self.0.load(Ordering::SeqCst))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a counter value for the next packet being sent.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn next(&self) -> CounterValue {
|
fn next(&self) -> CounterValue {
|
||||||
CounterValue(self.0.fetch_add(1, Ordering::SeqCst))
|
CounterValue(self.0.fetch_add(1, Ordering::SeqCst))
|
||||||
|
@ -1245,9 +1270,11 @@ impl Counter {
|
||||||
|
|
||||||
/// A value of the outgoing packet counter.
|
/// A value of the outgoing packet counter.
|
||||||
///
|
///
|
||||||
/// The counter is internally 64-bit so we can more easily track usage limits without
|
/// The used portion of the packet counter is the least significant 32 bits, but the internal
|
||||||
/// confusing logic to handle 32-bit wrapping. The least significant 32 bits are the
|
/// counter state is kept as a 64-bit integer. This makes it easier to correctly handle
|
||||||
/// actual counter put in the packet.
|
/// key expiration after usage limits are reached without complicated logic to handle 32-bit
|
||||||
|
/// wrapping. Usage limits are below 2^32 so the actual 32-bit counter will not wrap for a
|
||||||
|
/// given shared secret key.
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
struct CounterValue(u64);
|
struct CounterValue(u64);
|
||||||
|
@ -1259,30 +1286,44 @@ impl CounterValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Temporary object to construct a "pseudo-header" for AES-GCM nonce and HMAC calculation.
|
/// "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.
|
||||||
|
/// Fragmentation is not considered when authenticating the entire packet. A separate header
|
||||||
|
/// check code is used to make fragmentation itself more robust, but that's outside the scope
|
||||||
|
/// of AEAD authentication.
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
#[repr(C, packed)]
|
#[repr(C, packed)]
|
||||||
struct Pseudoheader(u64, u32);
|
struct CanonicalHeader(u64, u32);
|
||||||
|
|
||||||
impl Pseudoheader {
|
impl CanonicalHeader {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn make(session_id: u64, packet_type: u8, counter: u32) -> Self {
|
pub fn make(session_id: SessionId, packet_type: u8, counter: u32) -> Self {
|
||||||
Pseudoheader((session_id | (packet_type as u64)).to_le(), counter.to_le())
|
CanonicalHeader(
|
||||||
|
(u64::from(session_id) | (packet_type as u64).wrapping_shl(48)).to_le(),
|
||||||
|
counter.to_le(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn as_bytes(&self) -> &[u8; 12] {
|
||||||
|
memory::as_byte_array(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ephemeral offer sent with KEY_OFFER and rememebered so state can be reconstructed on COUNTER_OFFER.
|
/// Alice's KEY_OFFER, remembered so Noise agreement process can resume on KEY_COUNTER_OFFER.
|
||||||
struct EphemeralOffer {
|
struct EphemeralOffer {
|
||||||
id: [u8; 16],
|
id: [u8; 16], // Arbitrary random offer ID
|
||||||
creation_time: i64,
|
creation_time: i64, // Local time when offer was created
|
||||||
ratchet_count: u64,
|
ratchet_count: u64, // Ratchet count starting at zero for initial offer
|
||||||
ratchet_key: Option<Secret<64>>,
|
ratchet_key: Option<Secret<64>>, // Ratchet key from previous offer
|
||||||
key: Secret<64>,
|
key: Secret<64>, // Shared secret in-progress, at state after offer sent
|
||||||
alice_e0_keypair: P384KeyPair,
|
alice_e0_keypair: P384KeyPair, // NIST P-384 key pair (Noise ephemeral key for Alice)
|
||||||
alice_e1_keypair: Option<pqc_kyber::Keypair>,
|
alice_e1_keypair: Option<pqc_kyber::Keypair>, // Kyber1024 key pair (agreement result mixed post-Noise)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_initial_offer<SendFunction: FnMut(&mut [u8])>(
|
/// Create and send an ephemeral offer, returning the EphemeralOffer part that must be saved.
|
||||||
|
fn send_ephemeral_offer<SendFunction: FnMut(&mut [u8])>(
|
||||||
send: &mut SendFunction,
|
send: &mut SendFunction,
|
||||||
counter: CounterValue,
|
counter: CounterValue,
|
||||||
alice_session_id: SessionId,
|
alice_session_id: SessionId,
|
||||||
|
@ -1297,26 +1338,30 @@ fn create_initial_offer<SendFunction: FnMut(&mut [u8])>(
|
||||||
mtu: usize,
|
mtu: usize,
|
||||||
current_time: i64,
|
current_time: i64,
|
||||||
) -> Result<Box<EphemeralOffer>, Error> {
|
) -> Result<Box<EphemeralOffer>, Error> {
|
||||||
|
// Generate a NIST P-384 pair.
|
||||||
let alice_e0_keypair = P384KeyPair::generate();
|
let alice_e0_keypair = P384KeyPair::generate();
|
||||||
let e0s = alice_e0_keypair.agree(bob_s_public_p384);
|
|
||||||
if e0s.is_none() {
|
|
||||||
return Err(Error::InvalidPacket);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Perform key agreement with the other side's static P-384 public key.
|
||||||
|
let e0s = alice_e0_keypair.agree(bob_s_public_p384).ok_or(Error::InvalidPacket)?;
|
||||||
|
|
||||||
|
// Generate a Kyber1024 pair if enabled.
|
||||||
let alice_e1_keypair = if JEDI {
|
let alice_e1_keypair = if JEDI {
|
||||||
Some(pqc_kyber::keypair(&mut random::SecureRandom::get()))
|
Some(pqc_kyber::keypair(&mut random::SecureRandom::get()))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Get ratchet key for current key if one exists.
|
||||||
let (ratchet_key, ratchet_count) = if let Some(current_key) = current_key {
|
let (ratchet_key, ratchet_count) = if let Some(current_key) = current_key {
|
||||||
(Some(current_key.ratchet_key.clone()), current_key.ratchet_count)
|
(Some(current_key.ratchet_key.clone()), current_key.ratchet_count)
|
||||||
} else {
|
} else {
|
||||||
(None, 0)
|
(None, 0)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Random ephemeral offer ID
|
||||||
let id: [u8; 16] = random::get_bytes_secure();
|
let id: [u8; 16] = random::get_bytes_secure();
|
||||||
|
|
||||||
|
// Create ephemeral offer packet (not fragmented yet).
|
||||||
const PACKET_BUF_SIZE: usize = MIN_TRANSPORT_MTU * KEY_EXCHANGE_MAX_FRAGMENTS;
|
const PACKET_BUF_SIZE: usize = MIN_TRANSPORT_MTU * KEY_EXCHANGE_MAX_FRAGMENTS;
|
||||||
let mut packet_buf = [0_u8; PACKET_BUF_SIZE];
|
let mut packet_buf = [0_u8; PACKET_BUF_SIZE];
|
||||||
let mut packet_len = {
|
let mut packet_len = {
|
||||||
|
@ -1339,7 +1384,7 @@ fn create_initial_offer<SendFunction: FnMut(&mut [u8])>(
|
||||||
}
|
}
|
||||||
if let Some(ratchet_key) = ratchet_key.as_ref() {
|
if let Some(ratchet_key) = ratchet_key.as_ref() {
|
||||||
p.write_all(&[0x01])?;
|
p.write_all(&[0x01])?;
|
||||||
p.write_all(&key_fingerprint(ratchet_key.as_bytes())[..16])?;
|
p.write_all(&secret_fingerprint(ratchet_key.as_bytes())[..16])?;
|
||||||
} else {
|
} else {
|
||||||
p.write_all(&[0x00])?;
|
p.write_all(&[0x00])?;
|
||||||
}
|
}
|
||||||
|
@ -1347,42 +1392,44 @@ fn create_initial_offer<SendFunction: FnMut(&mut [u8])>(
|
||||||
PACKET_BUF_SIZE - p.len()
|
PACKET_BUF_SIZE - p.len()
|
||||||
};
|
};
|
||||||
|
|
||||||
let bob_session_id: u64 = bob_session_id.map_or(SessionId::NIL.0, |i| i.into());
|
// Create ephemeral agreement secret.
|
||||||
create_packet_header(&mut packet_buf, packet_len, mtu, PACKET_TYPE_KEY_OFFER, bob_session_id, counter)?;
|
|
||||||
let pseudoheader = Pseudoheader::make(bob_session_id, PACKET_TYPE_KEY_OFFER, counter.to_u32());
|
|
||||||
|
|
||||||
let key = Secret(hmac_sha512(
|
let key = Secret(hmac_sha512(
|
||||||
&hmac_sha512(&INITIAL_KEY, alice_e0_keypair.public_key_bytes()),
|
&hmac_sha512(&INITIAL_KEY, alice_e0_keypair.public_key_bytes()),
|
||||||
e0s.unwrap().as_bytes(),
|
e0s.as_bytes(),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
let bob_session_id = bob_session_id.unwrap_or(SessionId::NIL);
|
||||||
|
create_packet_header(&mut packet_buf, packet_len, mtu, PACKET_TYPE_KEY_OFFER, bob_session_id, counter)?;
|
||||||
|
|
||||||
|
let canonical_header = CanonicalHeader::make(bob_session_id, PACKET_TYPE_KEY_OFFER, counter.to_u32());
|
||||||
|
|
||||||
|
// Encrypt packet and attach AES-GCM tag.
|
||||||
let gcm_tag = {
|
let gcm_tag = {
|
||||||
let mut c = AesGcm::new(
|
let mut c = AesGcm::new(
|
||||||
kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB).first_n::<32>(),
|
kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB).first_n::<32>(),
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
c.init(memory::as_byte_array::<Pseudoheader, 12>(&pseudoheader));
|
c.init(canonical_header.as_bytes());
|
||||||
c.crypt_in_place(&mut packet_buf[(HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE)..packet_len]);
|
c.crypt_in_place(&mut packet_buf[(HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE)..packet_len]);
|
||||||
c.finish_encrypt()
|
c.finish_encrypt()
|
||||||
};
|
};
|
||||||
packet_buf[packet_len..(packet_len + AES_GCM_TAG_SIZE)].copy_from_slice(&gcm_tag);
|
packet_buf[packet_len..(packet_len + AES_GCM_TAG_SIZE)].copy_from_slice(&gcm_tag);
|
||||||
packet_len += AES_GCM_TAG_SIZE;
|
packet_len += AES_GCM_TAG_SIZE;
|
||||||
|
|
||||||
|
// Mix in static secret.
|
||||||
let key = Secret(hmac_sha512(key.as_bytes(), ss.as_bytes()));
|
let key = Secret(hmac_sha512(key.as_bytes(), ss.as_bytes()));
|
||||||
|
|
||||||
|
// HMAC packet using static + ephemeral key.
|
||||||
let hmac = hmac_sha384_2(
|
let hmac = hmac_sha384_2(
|
||||||
kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_HMAC).first_n::<48>(),
|
kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_HMAC).first_n::<48>(),
|
||||||
memory::as_byte_array::<Pseudoheader, 12>(&pseudoheader),
|
canonical_header.as_bytes(),
|
||||||
&packet_buf[HEADER_SIZE..packet_len],
|
&packet_buf[HEADER_SIZE..packet_len],
|
||||||
);
|
);
|
||||||
packet_buf[packet_len..(packet_len + HMAC_SIZE)].copy_from_slice(&hmac);
|
packet_buf[packet_len..(packet_len + HMAC_SIZE)].copy_from_slice(&hmac);
|
||||||
packet_len += HMAC_SIZE;
|
packet_len += HMAC_SIZE;
|
||||||
|
|
||||||
let hmac = hmac_sha384_2(
|
// Add secondary HMAC to verify that the caller knows the recipient's full static public identity.
|
||||||
bob_s_public_hash,
|
let hmac = hmac_sha384_2(bob_s_public_hash, canonical_header.as_bytes(), &packet_buf[HEADER_SIZE..packet_len]);
|
||||||
memory::as_byte_array::<Pseudoheader, 12>(&pseudoheader),
|
|
||||||
&packet_buf[HEADER_SIZE..packet_len],
|
|
||||||
);
|
|
||||||
packet_buf[packet_len..(packet_len + HMAC_SIZE)].copy_from_slice(&hmac);
|
packet_buf[packet_len..(packet_len + HMAC_SIZE)].copy_from_slice(&hmac);
|
||||||
packet_len += HMAC_SIZE;
|
packet_len += HMAC_SIZE;
|
||||||
|
|
||||||
|
@ -1399,13 +1446,14 @@ fn create_initial_offer<SendFunction: FnMut(&mut [u8])>(
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Populate all but the header check code in the first 16 bytes of a packet or fragment.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn create_packet_header(
|
fn create_packet_header(
|
||||||
header: &mut [u8],
|
header: &mut [u8],
|
||||||
packet_len: usize,
|
packet_len: usize,
|
||||||
mtu: usize,
|
mtu: usize,
|
||||||
packet_type: u8,
|
packet_type: u8,
|
||||||
recipient_session_id: u64,
|
recipient_session_id: SessionId,
|
||||||
counter: CounterValue,
|
counter: CounterValue,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let fragment_count = ((packet_len as f32) / (mtu - HEADER_SIZE) as f32).ceil() as usize;
|
let fragment_count = ((packet_len as f32) / (mtu - HEADER_SIZE) as f32).ceil() as usize;
|
||||||
|
@ -1414,14 +1462,21 @@ fn create_packet_header(
|
||||||
debug_assert!(mtu >= MIN_TRANSPORT_MTU);
|
debug_assert!(mtu >= MIN_TRANSPORT_MTU);
|
||||||
debug_assert!(packet_len >= MIN_PACKET_SIZE);
|
debug_assert!(packet_len >= MIN_PACKET_SIZE);
|
||||||
debug_assert!(fragment_count > 0);
|
debug_assert!(fragment_count > 0);
|
||||||
|
debug_assert!(fragment_count <= MAX_FRAGMENTS);
|
||||||
debug_assert!(packet_type <= 0x0f); // packet type is 4 bits
|
debug_assert!(packet_type <= 0x0f); // packet type is 4 bits
|
||||||
debug_assert!(recipient_session_id <= 0xffffffffffff); // session ID is 48 bits
|
|
||||||
|
|
||||||
if fragment_count <= MAX_FRAGMENTS {
|
if fragment_count <= MAX_FRAGMENTS {
|
||||||
// CCCC____IIIIIITF
|
// Header indexed by bit:
|
||||||
|
// [0-31] counter
|
||||||
|
// [32-63] header check code (computed later)
|
||||||
|
// [64-111] recipient's session ID (unique on their side)
|
||||||
|
// [112-115] packet type (0-15)
|
||||||
|
// [116-121] number of fragments (0..63 for 1..64 fragments total)
|
||||||
|
// [122-127] fragment number (0, 1, 2, ...)
|
||||||
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(
|
||||||
(recipient_session_id | (packet_type as u64).wrapping_shl(48) | ((fragment_count - 1) as u64).wrapping_shl(52)).to_le(),
|
(u64::from(recipient_session_id) | (packet_type as u64).wrapping_shl(48) | ((fragment_count - 1) as u64).wrapping_shl(52))
|
||||||
|
.to_le(),
|
||||||
&mut header[8..],
|
&mut header[8..],
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1431,6 +1486,7 @@ fn create_packet_header(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Break a packet into fragments and send them all.
|
||||||
fn send_with_fragmentation<SendFunction: FnMut(&mut [u8])>(
|
fn send_with_fragmentation<SendFunction: FnMut(&mut [u8])>(
|
||||||
send: &mut SendFunction,
|
send: &mut SendFunction,
|
||||||
packet: &mut [u8],
|
packet: &mut [u8],
|
||||||
|
@ -1443,7 +1499,7 @@ fn send_with_fragmentation<SendFunction: FnMut(&mut [u8])>(
|
||||||
let mut header: [u8; 16] = packet[..HEADER_SIZE].try_into().unwrap();
|
let mut header: [u8; 16] = packet[..HEADER_SIZE].try_into().unwrap();
|
||||||
loop {
|
loop {
|
||||||
let fragment = &mut packet[fragment_start..fragment_end];
|
let fragment = &mut packet[fragment_start..fragment_end];
|
||||||
set_header_mac(fragment, header_check_cipher);
|
set_header_check_code(fragment, header_check_cipher);
|
||||||
send(fragment);
|
send(fragment);
|
||||||
if fragment_end < packet_len {
|
if fragment_end < packet_len {
|
||||||
debug_assert!(header[15].wrapping_shr(2) < 63);
|
debug_assert!(header[15].wrapping_shr(2) < 63);
|
||||||
|
@ -1458,17 +1514,18 @@ fn send_with_fragmentation<SendFunction: FnMut(&mut [u8])>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set 32-bit header MAC.
|
/// Set 32-bit header check code, used to make fragmentation mechanism robust.
|
||||||
#[inline(always)]
|
#[inline]
|
||||||
fn set_header_mac(packet: &mut [u8], header_check_cipher: &Aes) {
|
fn set_header_check_code(packet: &mut [u8], header_check_cipher: &Aes) {
|
||||||
debug_assert!(packet.len() >= MIN_PACKET_SIZE);
|
debug_assert!(packet.len() >= MIN_PACKET_SIZE);
|
||||||
let mut header_mac = 0u128.to_ne_bytes();
|
let mut check_code = 0u128.to_ne_bytes();
|
||||||
header_check_cipher.encrypt_block(&packet[8..24], &mut header_mac);
|
header_check_cipher.encrypt_block(&packet[8..24], &mut check_code);
|
||||||
packet[4..8].copy_from_slice(&header_mac[..4]);
|
packet[4..8].copy_from_slice(&check_code[..4]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check 32-bit header MAC on an incoming packet.
|
/// Verify 32-bit header check code.
|
||||||
fn check_header_mac(packet: &[u8], header_check_cipher: &Aes) -> bool {
|
#[inline]
|
||||||
|
fn verify_header_check_code(packet: &[u8], header_check_cipher: &Aes) -> bool {
|
||||||
debug_assert!(packet.len() >= MIN_PACKET_SIZE);
|
debug_assert!(packet.len() >= MIN_PACKET_SIZE);
|
||||||
let mut header_mac = 0u128.to_ne_bytes();
|
let mut header_mac = 0u128.to_ne_bytes();
|
||||||
header_check_cipher.encrypt_block(&packet[8..24], &mut header_mac);
|
header_check_cipher.encrypt_block(&packet[8..24], &mut header_mac);
|
||||||
|
@ -1544,12 +1601,15 @@ fn parse_key_offer_after_header(
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
/// initiated first.
|
||||||
enum Role {
|
enum Role {
|
||||||
Alice,
|
Alice,
|
||||||
Bob,
|
Bob,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specialized class for the careful management of key lifetimes.
|
/// Key lifetime manager state and logic (separate to spotlight and keep clean)
|
||||||
struct KeyLifetime {
|
struct KeyLifetime {
|
||||||
rekey_at_or_after_counter: u64,
|
rekey_at_or_after_counter: u64,
|
||||||
hard_expire_at_counter: u64,
|
hard_expire_at_counter: u64,
|
||||||
|
@ -1580,19 +1640,19 @@ impl KeyLifetime {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
/// A shared symmetric session key.
|
||||||
struct SessionKey {
|
struct SessionKey {
|
||||||
fingerprint: [u8; 16],
|
secret_fingerprint: [u8; 16], // First 128 bits of a SHA384 computed from the secret
|
||||||
establish_time: i64,
|
establish_time: i64, // Time session key was established
|
||||||
establish_counter: u64,
|
establish_counter: u64, // Counter value at which session was established
|
||||||
lifetime: KeyLifetime,
|
lifetime: KeyLifetime, // Key expiration time and counter
|
||||||
ratchet_key: Secret<64>,
|
ratchet_key: Secret<64>, // Ratchet key for deriving the next session key
|
||||||
receive_key: Secret<32>,
|
receive_key: Secret<32>, // Receive side AES-GCM key
|
||||||
send_key: Secret<32>,
|
send_key: Secret<32>, // Send side AES-GCM key
|
||||||
receive_cipher_pool: Mutex<Vec<Box<AesGcm>>>,
|
receive_cipher_pool: Mutex<Vec<Box<AesGcm>>>, // Pool of initialized sending ciphers
|
||||||
send_cipher_pool: Mutex<Vec<Box<AesGcm>>>,
|
send_cipher_pool: Mutex<Vec<Box<AesGcm>>>, // Pool of initialized receiving ciphers
|
||||||
ratchet_count: u64,
|
ratchet_count: u64, // Number of new keys negotiated in this session
|
||||||
jedi: bool, // true if kyber was enabled on both sides
|
jedi: bool, // True if Kyber1024 was used (both sides enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SessionKey {
|
impl SessionKey {
|
||||||
|
@ -1605,7 +1665,7 @@ impl SessionKey {
|
||||||
Role::Bob => (a2b, b2a),
|
Role::Bob => (a2b, b2a),
|
||||||
};
|
};
|
||||||
Self {
|
Self {
|
||||||
fingerprint: key_fingerprint(key.as_bytes())[..16].try_into().unwrap(),
|
secret_fingerprint: secret_fingerprint(key.as_bytes())[..16].try_into().unwrap(),
|
||||||
establish_time: current_time,
|
establish_time: current_time,
|
||||||
establish_counter: current_counter.0,
|
establish_counter: current_counter.0,
|
||||||
lifetime: KeyLifetime::new(current_counter, current_time),
|
lifetime: KeyLifetime::new(current_counter, current_time),
|
||||||
|
@ -1619,7 +1679,7 @@ impl SessionKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[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(self
|
Ok(self
|
||||||
|
@ -1638,12 +1698,12 @@ impl SessionKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline]
|
||||||
fn return_send_cipher(&self, c: Box<AesGcm>) {
|
fn return_send_cipher(&self, c: Box<AesGcm>) {
|
||||||
self.send_cipher_pool.lock().unwrap().push(c);
|
self.send_cipher_pool.lock().unwrap().push(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[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()
|
||||||
|
@ -1652,7 +1712,7 @@ impl SessionKey {
|
||||||
.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(always)]
|
#[inline]
|
||||||
fn return_receive_cipher(&self, c: Box<AesGcm>) {
|
fn return_receive_cipher(&self, c: Box<AesGcm>) {
|
||||||
self.receive_cipher_pool.lock().unwrap().push(c);
|
self.receive_cipher_pool.lock().unwrap().push(c);
|
||||||
}
|
}
|
||||||
|
@ -1667,14 +1727,13 @@ fn hmac_sha384_2(key: &[u8], a: &[u8], b: &[u8]) -> [u8; 48] {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// HMAC-SHA512 key derivation function modeled on: https://csrc.nist.gov/publications/detail/sp/800-108/final (page 12)
|
/// HMAC-SHA512 key derivation function modeled on: https://csrc.nist.gov/publications/detail/sp/800-108/final (page 12)
|
||||||
|
/// Cryptographically this isn't really different from HMAC(key, [label]) with just one byte.
|
||||||
fn kbkdf512(key: &[u8], label: u8) -> Secret<64> {
|
fn kbkdf512(key: &[u8], label: u8) -> Secret<64> {
|
||||||
Secret(hmac_sha512(key, &[0, 0, 0, 0, b'Z', b'T', label, 0, 0, 0, 0, 0x02, 0x00]))
|
Secret(hmac_sha512(key, &[0, 0, 0, 0, b'Z', b'T', label, 0, 0, 0, 0, 0x02, 0x00]))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a hash of a secret key that can be used as a public fingerprint.
|
/// Get a hash of a secret key that can be used as a public fingerprint.
|
||||||
///
|
fn secret_fingerprint(key: &[u8]) -> [u8; 48] {
|
||||||
/// This just needs to be a hash that will never be the same as any hash or HMAC used for actual key derivation.
|
|
||||||
fn key_fingerprint(key: &[u8]) -> [u8; 48] {
|
|
||||||
let mut tmp = SHA384::new();
|
let mut tmp = SHA384::new();
|
||||||
tmp.update("fp".as_bytes());
|
tmp.update("fp".as_bytes());
|
||||||
tmp.update(key);
|
tmp.update(key);
|
||||||
|
|
Loading…
Add table
Reference in a new issue