mirror of
https://github.com/ZeroTier/ZeroTierOne
synced 2025-08-22 22:33:58 -07:00
A whole lot more Noise_XK work... exchange almost done.
This commit is contained in:
parent
a914376ad4
commit
c97d5d28bb
10 changed files with 656 additions and 240 deletions
|
@ -46,7 +46,7 @@ mod fruit_flavored {
|
||||||
data_out_len: usize,
|
data_out_len: usize,
|
||||||
data_out_written: *mut usize,
|
data_out_written: *mut usize,
|
||||||
) -> i32;
|
) -> i32;
|
||||||
//fn CCCryptorReset(cryptor_ref: *mut c_void, iv: *const c_void) -> i32;
|
fn CCCryptorReset(cryptor_ref: *mut c_void, iv: *const c_void) -> i32;
|
||||||
fn CCCryptorRelease(cryptor_ref: *mut c_void) -> i32;
|
fn CCCryptorRelease(cryptor_ref: *mut c_void) -> i32;
|
||||||
fn CCCryptorGCMSetIV(cryptor_ref: *mut c_void, iv: *const c_void, iv_len: usize) -> i32;
|
fn CCCryptorGCMSetIV(cryptor_ref: *mut c_void, iv: *const c_void, iv_len: usize) -> i32;
|
||||||
fn CCCryptorGCMAddAAD(cryptor_ref: *mut c_void, aad: *const c_void, len: usize) -> i32;
|
fn CCCryptorGCMAddAAD(cryptor_ref: *mut c_void, aad: *const c_void, len: usize) -> i32;
|
||||||
|
@ -183,6 +183,101 @@ mod fruit_flavored {
|
||||||
unsafe impl Send for Aes {}
|
unsafe impl Send for Aes {}
|
||||||
unsafe impl Sync for Aes {}
|
unsafe impl Sync for Aes {}
|
||||||
|
|
||||||
|
pub struct AesCtr(*mut c_void);
|
||||||
|
|
||||||
|
impl Drop for AesCtr {
|
||||||
|
#[inline(always)]
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { CCCryptorRelease(self.0) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AesCtr {
|
||||||
|
/// Construct a new AES-CTR cipher.
|
||||||
|
/// Key must be 16, 24, or 32 bytes in length or a panic will occur.
|
||||||
|
pub fn new(k: &[u8]) -> Self {
|
||||||
|
if k.len() != 32 && k.len() != 24 && k.len() != 16 {
|
||||||
|
panic!("AES supports 128, 192, or 256 bits keys");
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
let mut ptr: *mut c_void = null_mut();
|
||||||
|
let result = CCCryptorCreateWithMode(
|
||||||
|
kCCEncrypt,
|
||||||
|
kCCModeCTR,
|
||||||
|
kCCAlgorithmAES,
|
||||||
|
0,
|
||||||
|
[0_u64; 2].as_ptr().cast(),
|
||||||
|
k.as_ptr().cast(),
|
||||||
|
k.len(),
|
||||||
|
null(),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&mut ptr,
|
||||||
|
);
|
||||||
|
if result != 0 {
|
||||||
|
panic!("CCCryptorCreateWithMode for CTR mode returned {}", result);
|
||||||
|
}
|
||||||
|
AesCtr(ptr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize AES-CTR for encryption or decryption with the given IV.
|
||||||
|
/// If it's already been used, this also resets the cipher. There is no separate reset.
|
||||||
|
pub fn reset_set_iv(&mut self, iv: &[u8]) {
|
||||||
|
unsafe {
|
||||||
|
if iv.len() == 16 {
|
||||||
|
if CCCryptorReset(self.0, iv.as_ptr().cast()) != 0 {
|
||||||
|
panic!("CCCryptorReset for CTR mode failed (old MacOS bug)");
|
||||||
|
}
|
||||||
|
} else if iv.len() < 16 {
|
||||||
|
let mut iv2 = [0_u8; 16];
|
||||||
|
iv2[0..iv.len()].copy_from_slice(iv);
|
||||||
|
if CCCryptorReset(self.0, iv2.as_ptr().cast()) != 0 {
|
||||||
|
panic!("CCCryptorReset for CTR mode failed (old MacOS bug)");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("CTR IV must be less than or equal to 16 bytes in length");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encrypt or decrypt (same operation with CTR mode)
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn crypt(&mut self, input: &[u8], output: &mut [u8]) {
|
||||||
|
unsafe {
|
||||||
|
assert!(output.len() >= input.len());
|
||||||
|
let mut data_out_written: usize = 0;
|
||||||
|
CCCryptorUpdate(
|
||||||
|
self.0,
|
||||||
|
input.as_ptr().cast(),
|
||||||
|
input.len(),
|
||||||
|
output.as_mut_ptr().cast(),
|
||||||
|
output.len(),
|
||||||
|
&mut data_out_written,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encrypt or decrypt in place (same operation with CTR mode)
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn crypt_in_place(&mut self, data: &mut [u8]) {
|
||||||
|
unsafe {
|
||||||
|
let mut data_out_written: usize = 0;
|
||||||
|
CCCryptorUpdate(
|
||||||
|
self.0,
|
||||||
|
data.as_ptr().cast(),
|
||||||
|
data.len(),
|
||||||
|
data.as_mut_ptr().cast(),
|
||||||
|
data.len(),
|
||||||
|
&mut data_out_written,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for AesCtr {}
|
||||||
|
|
||||||
pub struct AesGcm(*mut c_void, bool);
|
pub struct AesGcm(*mut c_void, bool);
|
||||||
|
|
||||||
impl Drop for AesGcm {
|
impl Drop for AesGcm {
|
||||||
|
@ -307,6 +402,17 @@ mod openssl_aes {
|
||||||
use std::cell::UnsafeCell;
|
use std::cell::UnsafeCell;
|
||||||
use std::mem::MaybeUninit;
|
use std::mem::MaybeUninit;
|
||||||
|
|
||||||
|
fn aes_ctr_by_key_size(ks: usize) -> Cipher {
|
||||||
|
match ks {
|
||||||
|
16 => Cipher::aes_128_ctr(),
|
||||||
|
24 => Cipher::aes_192_ctr(),
|
||||||
|
32 => Cipher::aes_256_ctr(),
|
||||||
|
_ => {
|
||||||
|
panic!("AES supports 128, 192, or 256 bits keys");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn aes_gcm_by_key_size(ks: usize) -> Cipher {
|
fn aes_gcm_by_key_size(ks: usize) -> Cipher {
|
||||||
match ks {
|
match ks {
|
||||||
16 => Cipher::aes_128_gcm(),
|
16 => Cipher::aes_128_gcm(),
|
||||||
|
@ -390,6 +496,53 @@ mod openssl_aes {
|
||||||
unsafe impl Send for Aes {}
|
unsafe impl Send for Aes {}
|
||||||
unsafe impl Sync for Aes {}
|
unsafe impl Sync for Aes {}
|
||||||
|
|
||||||
|
pub struct AesCtr(Secret<32>, usize, Option<Crypter>);
|
||||||
|
|
||||||
|
impl AesCtr {
|
||||||
|
/// Construct a new AES-CTR cipher.
|
||||||
|
/// Key must be 16, 24, or 32 bytes in length or a panic will occur.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn new(k: &[u8]) -> Self {
|
||||||
|
let mut s: Secret<32> = Secret::default();
|
||||||
|
match k.len() {
|
||||||
|
16 | 24 | 32 => {
|
||||||
|
s.0[..k.len()].copy_from_slice(k);
|
||||||
|
Self(s, k.len(), None)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
panic!("AES supports 128, 192, or 256 bits keys");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize AES-CTR for encryption or decryption with the given IV.
|
||||||
|
/// If it's already been used, this also resets the cipher. There is no separate reset.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn reset_set_iv(&mut self, iv: &[u8]) {
|
||||||
|
let mut c = Crypter::new(aes_ctr_by_key_size(self.1), Mode::Encrypt, &self.0 .0[..self.1], Some(iv)).unwrap();
|
||||||
|
c.pad(false);
|
||||||
|
let _ = self.2.replace(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encrypt or decrypt (same operation with CTR mode)
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn crypt(&mut self, input: &[u8], output: &mut [u8]) {
|
||||||
|
let _ = self.2.as_mut().unwrap().update(input, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encrypt or decrypt in place (same operation with CTR mode)
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn crypt_in_place(&mut self, data: &mut [u8]) {
|
||||||
|
let _ = self
|
||||||
|
.2
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.update(unsafe { &*std::slice::from_raw_parts(data.as_ptr(), data.len()) }, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for AesCtr {}
|
||||||
|
|
||||||
pub struct AesGcm(Secret<32>, usize, CipherCtx, bool);
|
pub struct AesGcm(Secret<32>, usize, CipherCtx, bool);
|
||||||
|
|
||||||
impl AesGcm {
|
impl AesGcm {
|
||||||
|
@ -479,10 +632,10 @@ mod openssl_aes {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub use fruit_flavored::{Aes, AesGcm};
|
pub use fruit_flavored::{Aes, AesCtr, AesGcm};
|
||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
pub use openssl_aes::{Aes, AesGcm};
|
pub use openssl_aes::{Aes, AesCtr, AesGcm};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
|
@ -27,3 +27,13 @@ pub fn secure_eq<A: AsRef<[u8]> + ?Sized, B: AsRef<[u8]> + ?Sized>(a: &A, b: &B)
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
fn OPENSSL_cleanse(ptr: *mut std::ffi::c_void, len: usize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Destroy the contents of some memory
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn burn(b: &mut [u8]) {
|
||||||
|
unsafe { OPENSSL_cleanse(b.as_mut_ptr().cast(), b.len()) };
|
||||||
|
}
|
||||||
|
|
|
@ -37,6 +37,11 @@ impl<const L: usize> Secret<L> {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn as_bytes_mut(&mut self) -> &mut [u8; L] {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the first N bytes of this secret as a fixed length array.
|
/// Get the first N bytes of this secret as a fixed length array.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn first_n<const N: usize>(&self) -> &[u8; N] {
|
pub fn first_n<const N: usize>(&self) -> &[u8; N] {
|
||||||
|
|
|
@ -33,6 +33,13 @@ pub trait ApplicationLayer: Sized {
|
||||||
/// Rate limit for attempts to rekey existing sessions in milliseconds (default: 2000).
|
/// Rate limit for attempts to rekey existing sessions in milliseconds (default: 2000).
|
||||||
const REKEY_RATE_LIMIT_MS: i64 = 2000;
|
const REKEY_RATE_LIMIT_MS: i64 = 2000;
|
||||||
|
|
||||||
|
/// Extract the NIST P-384 ECC public key component from a static public key blob or return None on failure.
|
||||||
|
///
|
||||||
|
/// This is called to parse the static public key blob from the other end and extract its NIST P-384 public
|
||||||
|
/// key. SECURITY NOTE: the information supplied here is from the wire so care must be taken to parse it
|
||||||
|
/// safely and fail on any error or corruption.
|
||||||
|
fn extract_s_public_from_raw(static_public: &[u8]) -> Option<P384PublicKey>;
|
||||||
|
|
||||||
/// Get a reference to this host's static public key blob.
|
/// Get a reference to this host's static public key blob.
|
||||||
///
|
///
|
||||||
/// This must contain a NIST P-384 public key but can contain other information. In ZeroTier this
|
/// This must contain a NIST P-384 public key but can contain other information. In ZeroTier this
|
||||||
|
@ -49,27 +56,21 @@ pub trait ApplicationLayer: Sized {
|
||||||
/// This must return the NIST P-384 public key that is contained within the static public key blob.
|
/// This must return the NIST P-384 public key that is contained within the static public key blob.
|
||||||
fn get_local_s_keypair(&self) -> &P384KeyPair;
|
fn get_local_s_keypair(&self) -> &P384KeyPair;
|
||||||
|
|
||||||
/// Extract the NIST P-384 ECC public key component from a static public key blob or return None on failure.
|
|
||||||
///
|
|
||||||
/// This is called to parse the static public key blob from the other end and extract its NIST P-384 public
|
|
||||||
/// key. SECURITY NOTE: the information supplied here is from the wire so care must be taken to parse it
|
|
||||||
/// safely and fail on any error or corruption.
|
|
||||||
fn extract_s_public_from_raw(static_public: &[u8]) -> Option<P384PublicKey>;
|
|
||||||
|
|
||||||
/// Look up a local session by local session ID or return None if not found.
|
/// Look up a local session by local session ID or return None if not found.
|
||||||
fn lookup_session<'a>(&self, local_session_id: SessionId) -> Option<Self::SessionRef<'a>>;
|
fn lookup_session<'a>(&self, local_session_id: SessionId) -> Option<Self::SessionRef<'a>>;
|
||||||
|
|
||||||
/// Rate limit and check an attempted new session (called before accept_new_session).
|
/// Rate limit and check an attempted new session.
|
||||||
fn check_new_session(&self, rc: &ReceiveContext<Self>, remote_address: &Self::RemoteAddress) -> bool;
|
fn prescreen_new_session(&self, rc: &ReceiveContext<Self>, remote_address: &Self::RemoteAddress) -> bool;
|
||||||
|
|
||||||
/// Get a new locally unique session ID.
|
|
||||||
fn new_session(&self, remote_address: &Self::RemoteAddress) -> Option<(SessionId, Self::Data)>;
|
|
||||||
|
|
||||||
/// Check whether a new session should be accepted.
|
/// Check whether a new session should be accepted.
|
||||||
///
|
///
|
||||||
/// On success a tuple of local session ID, static secret, and associated object is returned. The
|
/// This is called in the final phase of Noise_XK once Alice's identity is known. If it should be
|
||||||
/// static secret is whatever results from agreement between the local and remote static public
|
/// accepted then this should return a session ID, a PSK or all zeroes if there is none, and application
|
||||||
/// keys.
|
/// specific data to attach to the session.
|
||||||
|
///
|
||||||
|
/// This doesn't guarantee acceptance until the new session is returned from the receive call, since
|
||||||
|
/// something can still go wrong during final authentication and session setup. The session ID supplied
|
||||||
|
/// here should not be assumed to be in use.
|
||||||
fn accept_new_session(
|
fn accept_new_session(
|
||||||
&self,
|
&self,
|
||||||
receive_context: &ReceiveContext<Self>,
|
receive_context: &ReceiveContext<Self>,
|
||||||
|
|
|
@ -52,6 +52,7 @@ pub(crate) const AES_KEY_SIZE: usize = 32;
|
||||||
pub(crate) const AES_HEADER_CHECK_KEY_SIZE: usize = 16;
|
pub(crate) const AES_HEADER_CHECK_KEY_SIZE: usize = 16;
|
||||||
pub(crate) const AES_GCM_TAG_SIZE: usize = 16;
|
pub(crate) const AES_GCM_TAG_SIZE: usize = 16;
|
||||||
pub(crate) const AES_GCM_NONCE_SIZE: usize = 12;
|
pub(crate) const AES_GCM_NONCE_SIZE: usize = 12;
|
||||||
|
pub(crate) const AES_CTR_NONCE_SIZE: usize = 12;
|
||||||
|
|
||||||
/// Size of a session ID, which behaves a bit like a TCP port number.
|
/// Size of a session ID, which behaves a bit like a TCP port number.
|
||||||
///
|
///
|
||||||
|
@ -61,6 +62,9 @@ pub(crate) const SESSION_ID_SIZE: usize = 6;
|
||||||
/// Maximum difference between out-of-order incoming packet counters, and size of deduplication buffer.
|
/// Maximum difference between out-of-order incoming packet counters, and size of deduplication buffer.
|
||||||
pub(crate) const COUNTER_WINDOW_MAX_OUT_OF_ORDER: usize = 16;
|
pub(crate) const COUNTER_WINDOW_MAX_OUT_OF_ORDER: usize = 16;
|
||||||
|
|
||||||
|
/// Timeout for a "note to self" that must be returned by a session initiator.
|
||||||
|
pub(crate) const BOB_NOTE_TO_SELF_TIMEOUT_MS: i64 = 500;
|
||||||
|
|
||||||
/// Maximum skip-ahead for counter.
|
/// Maximum skip-ahead for counter.
|
||||||
///
|
///
|
||||||
/// This is huge (2^24) because its real purpose is to filter out bad packets where decryption of
|
/// This is huge (2^24) because its real purpose is to filter out bad packets where decryption of
|
||||||
|
@ -68,7 +72,8 @@ pub(crate) const COUNTER_WINDOW_MAX_OUT_OF_ORDER: usize = 16;
|
||||||
pub(crate) const COUNTER_WINDOW_MAX_SKIP_AHEAD: u64 = 16777216;
|
pub(crate) const COUNTER_WINDOW_MAX_SKIP_AHEAD: u64 = 16777216;
|
||||||
|
|
||||||
// Key usage labels for sub-key derivation using NIST-style KBKDF (basically just HMAC KDF).
|
// Key usage labels for sub-key derivation using NIST-style KBKDF (basically just HMAC KDF).
|
||||||
pub(crate) const KBKDF_KEY_USAGE_LABEL_KEX_PAYLOAD_ENCRYPTION: u8 = b'M'; // intermediate keys used in key exchanges
|
pub(crate) const KBKDF_KEY_USAGE_LABEL_KEX_PAYLOAD_ENCRYPTION: u8 = b'X'; // intermediate keys used in key exchanges
|
||||||
|
pub(crate) const KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION: u8 = b'x'; // intermediate keys used in key exchanges
|
||||||
pub(crate) const KBKDF_KEY_USAGE_LABEL_HEADER_CHECK: u8 = b'H'; // AES-based header check code generation
|
pub(crate) const KBKDF_KEY_USAGE_LABEL_HEADER_CHECK: u8 = b'H'; // AES-based header check code generation
|
||||||
pub(crate) const KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB: u8 = b'A'; // AES-GCM in A->B direction
|
pub(crate) const KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB: u8 = b'A'; // AES-GCM in A->B direction
|
||||||
pub(crate) const KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE: u8 = b'B'; // AES-GCM in B->A direction
|
pub(crate) const KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE: u8 = b'B'; // AES-GCM in B->A direction
|
||||||
|
|
|
@ -43,6 +43,14 @@ pub enum Error {
|
||||||
UnexpectedBufferOverrun,
|
UnexpectedBufferOverrun,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// An I/O error in the parser means an invalid packet.
|
||||||
|
impl From<std::io::Error> for Error {
|
||||||
|
#[inline(always)]
|
||||||
|
fn from(_: std::io::Error) -> Self {
|
||||||
|
Self::InvalidPacket
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Error {
|
impl std::fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
|
|
|
@ -10,4 +10,4 @@ pub mod constants;
|
||||||
pub use crate::applicationlayer::ApplicationLayer;
|
pub use crate::applicationlayer::ApplicationLayer;
|
||||||
pub use crate::error::Error;
|
pub use crate::error::Error;
|
||||||
pub use crate::sessionid::SessionId;
|
pub use crate::sessionid::SessionId;
|
||||||
pub use crate::zssp::{ReceiveContext, ReceiveResult, Role, Session};
|
pub use crate::zssp::{ReceiveContext, ReceiveResult, Session};
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
|
|
||||||
use pqc_kyber::{KYBER_CIPHERTEXTBYTES, KYBER_PUBLICKEYBYTES};
|
use pqc_kyber::{KYBER_CIPHERTEXTBYTES, KYBER_PUBLICKEYBYTES};
|
||||||
use zerotier_crypto::p384::P384_PUBLIC_KEY_SIZE;
|
use zerotier_crypto::hash::{HMAC_SHA384_SIZE, SHA384_HASH_SIZE};
|
||||||
|
use zerotier_crypto::p384::{P384_PUBLIC_KEY_SIZE, P384_SECRET_KEY_SIZE};
|
||||||
|
|
||||||
use crate::applicationlayer::ApplicationLayer;
|
use crate::applicationlayer::ApplicationLayer;
|
||||||
use crate::constants::{AES_GCM_TAG_SIZE, HEADER_SIZE, MIN_PACKET_SIZE, SESSION_ID_SIZE};
|
use crate::constants::{AES_GCM_TAG_SIZE, HEADER_SIZE, MIN_PACKET_SIZE, SESSION_ID_SIZE};
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
|
|
||||||
/// Maximum packet size for handshake packets
|
|
||||||
///
|
|
||||||
/// Packed structs are padded to this size so they can be recast to byte arrays of this size.
|
|
||||||
pub(crate) const NOISE_MAX_HANDSHAKE_PACKET_SIZE: usize = 2048;
|
pub(crate) const NOISE_MAX_HANDSHAKE_PACKET_SIZE: usize = 2048;
|
||||||
|
|
||||||
pub(crate) const PACKET_TYPE_DATA: u8 = 0;
|
pub(crate) const PACKET_TYPE_DATA: u8 = 0;
|
||||||
|
@ -19,33 +17,25 @@ pub(crate) const PACKET_TYPE_ALICE_STATIC_ACK: u8 = 3;
|
||||||
pub(crate) const PACKET_TYPE_ALICE_REKEY_INIT: u8 = 4;
|
pub(crate) const PACKET_TYPE_ALICE_REKEY_INIT: u8 = 4;
|
||||||
pub(crate) const PACKET_TYPE_BOB_REKEY_ACK: u8 = 5;
|
pub(crate) const PACKET_TYPE_BOB_REKEY_ACK: u8 = 5;
|
||||||
|
|
||||||
pub(crate) const NOISE_XK_ALICE_EPHEMERAL_OFFER_ENCRYPTED_SECTION_START: usize = HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE;
|
|
||||||
pub(crate) const NOISE_XK_ALICE_EPHEMERAL_OFFER_ENCRYPTED_SECTION_END: usize =
|
|
||||||
NOISE_XK_ALICE_EPHEMERAL_OFFER_ENCRYPTED_SECTION_START + SESSION_ID_SIZE + KYBER_PUBLICKEYBYTES + 8;
|
|
||||||
pub(crate) const NOISE_XK_ALICE_EPHEMERAL_OFFER_SIZE: usize = NOISE_XK_ALICE_EPHEMERAL_OFFER_ENCRYPTED_SECTION_END + AES_GCM_TAG_SIZE;
|
|
||||||
|
|
||||||
pub(crate) trait ProtocolFlatBuffer {}
|
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
#[repr(C, packed)]
|
#[repr(C, packed)]
|
||||||
pub(crate) struct NoiseXKAliceEphemeralOffer {
|
pub(crate) struct NoiseXKAliceEphemeralOffer {
|
||||||
pub header: [u8; HEADER_SIZE],
|
pub header: [u8; HEADER_SIZE],
|
||||||
pub session_protocol_version: u8,
|
pub session_protocol_version: u8,
|
||||||
|
pub reserved: [u8; 8],
|
||||||
pub alice_noise_e: [u8; P384_PUBLIC_KEY_SIZE],
|
pub alice_noise_e: [u8; P384_PUBLIC_KEY_SIZE],
|
||||||
// -- start AES-GCM(es) encrypted section (IV is first 12 bytes of SHA384(alice_noise_e))
|
// -- start AES-CTR(es) encrypted section (IV is first 12 bytes of SHA384(alice_noise_e))
|
||||||
pub alice_session_id: [u8; SESSION_ID_SIZE],
|
pub alice_session_id: [u8; SESSION_ID_SIZE],
|
||||||
pub alice_hk_public: [u8; KYBER_PUBLICKEYBYTES],
|
pub alice_hk_public: [u8; KYBER_PUBLICKEYBYTES],
|
||||||
pub salt: [u8; 8],
|
pub salt: [u8; 8],
|
||||||
// -- end encrypted section
|
// -- end encrypted section
|
||||||
pub gcm_mac: [u8; 16],
|
pub hmac_es: [u8; HMAC_SHA384_SIZE],
|
||||||
_padding: [u8; NOISE_MAX_HANDSHAKE_PACKET_SIZE - NOISE_XK_ALICE_EPHEMERAL_OFFER_SIZE],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) const NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_ENCRYPTED_SECTION_START: usize = HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE;
|
impl NoiseXKAliceEphemeralOffer {
|
||||||
pub(crate) const NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_ENCRYPTED_SECTION_END: usize =
|
pub const ENC_START: usize = HEADER_SIZE + 1 + 8 + P384_PUBLIC_KEY_SIZE;
|
||||||
NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_ENCRYPTED_SECTION_START + SESSION_ID_SIZE + KYBER_CIPHERTEXTBYTES;
|
pub const AUTH_START: usize = size_of::<NoiseXKAliceEphemeralOffer>() - HMAC_SHA384_SIZE;
|
||||||
pub(crate) const NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_SIZE: usize =
|
}
|
||||||
NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_ENCRYPTED_SECTION_END + AES_GCM_TAG_SIZE;
|
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
#[repr(C, packed)]
|
#[repr(C, packed)]
|
||||||
|
@ -53,31 +43,62 @@ pub(crate) struct NoiseXKBobEphemeralCounterOffer {
|
||||||
pub header: [u8; HEADER_SIZE],
|
pub header: [u8; HEADER_SIZE],
|
||||||
pub session_protocol_version: u8,
|
pub session_protocol_version: u8,
|
||||||
pub bob_noise_e: [u8; P384_PUBLIC_KEY_SIZE],
|
pub bob_noise_e: [u8; P384_PUBLIC_KEY_SIZE],
|
||||||
// -- start AES-GCM(es_ee) encrypted section (IV is first 12 bytes of SHA384(bob_noise_e))
|
// -- start AES-CTR(es_ee) encrypted section (IV is first 12 bytes of SHA384(bob_noise_e))
|
||||||
pub bob_session_id: [u8; SESSION_ID_SIZE],
|
|
||||||
pub bob_hk_ciphertext: [u8; KYBER_CIPHERTEXTBYTES],
|
pub bob_hk_ciphertext: [u8; KYBER_CIPHERTEXTBYTES],
|
||||||
|
pub bob_note_to_self: [u8; size_of::<BobNoteToSelf>()],
|
||||||
// -- end encrypted sectiion
|
// -- end encrypted sectiion
|
||||||
pub gcm_mac: [u8; 16],
|
pub hmac_es_ee: [u8; HMAC_SHA384_SIZE],
|
||||||
_padding: [u8; NOISE_MAX_HANDSHAKE_PACKET_SIZE - NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_SIZE],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
impl NoiseXKBobEphemeralCounterOffer {
|
||||||
pub(crate) const NOISE_XK_ALICE_STATIC_ACK_FIXED_FIELDS_SIZE: usize = HEADER_SIZE + 1;
|
pub const ENC_START: usize = HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE;
|
||||||
|
pub const AUTH_START: usize = size_of::<NoiseXKBobEphemeralCounterOffer>() - HMAC_SHA384_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub(crate) struct BobNoteToSelf {
|
||||||
|
pub iv: [u8; 16],
|
||||||
|
// -- start AES-GCM encrypted section using ephemeral secret known only to Bob
|
||||||
|
pub timestamp: [u8; 8],
|
||||||
|
pub alice_session_id: [u8; SESSION_ID_SIZE],
|
||||||
|
pub bob_noise_e: [u8; P384_PUBLIC_KEY_SIZE],
|
||||||
|
pub bob_noise_e_secret: [u8; P384_SECRET_KEY_SIZE],
|
||||||
|
pub hk: [u8; 32],
|
||||||
|
pub noise_es_ee: [u8; 64],
|
||||||
|
// -- end encrypted sectiion
|
||||||
|
pub gcm_mac: [u8; AES_GCM_TAG_SIZE],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BobNoteToSelf {
|
||||||
|
pub const IV_SIZE: usize = 16;
|
||||||
|
pub const ENC_START: usize = Self::IV_SIZE;
|
||||||
|
pub const AUTH_START: usize = size_of::<BobNoteToSelf>() - AES_GCM_TAG_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// These are variable length and so they're only here for documentation purposes.
|
||||||
|
|
||||||
|
pub(crate) const NOISE_XK_ALICE_STATIC_ACK_BOB_NOTE_TO_SELF_START: usize = HEADER_SIZE + 1;
|
||||||
|
pub(crate) const NOISE_XK_ALICE_STATIC_ACK_BOB_NOTE_TO_SELF_END: usize = HEADER_SIZE + 1 + size_of::<BobNoteToSelf>();
|
||||||
|
pub(crate) const NOISE_XK_ALICE_STATIC_ACK_ENCRYPTED_SECTION_START: usize = NOISE_XK_ALICE_STATIC_ACK_BOB_NOTE_TO_SELF_END;
|
||||||
|
pub(crate) const NOISE_XK_ALICE_STATIC_ACK_MIN_SIZE: usize =
|
||||||
|
NOISE_XK_ALICE_STATIC_ACK_BOB_NOTE_TO_SELF_END + 2 + 2 + HMAC_SHA384_SIZE + HMAC_SHA384_SIZE;
|
||||||
|
|
||||||
|
/*
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
#[repr(C, packed)]
|
#[repr(C, packed)]
|
||||||
pub(crate) struct NoiseXKAliceStaticAck {
|
pub(crate) struct NoiseXKAliceStaticAck {
|
||||||
pub header: [u8; HEADER_SIZE],
|
pub header: [u8; HEADER_SIZE],
|
||||||
pub session_protocol_version: u8,
|
pub session_protocol_version: u8,
|
||||||
// -- start AES-GCM(es_ee) encrypted section (IV is first 12 bytes of SHA384(hk))
|
pub bob_note_to_self: [u8; size_of::<BobNoteToSelf>()],
|
||||||
_var_length_fields_and_padding: [u8; NOISE_MAX_HANDSHAKE_PACKET_SIZE - NOISE_XK_ALICE_STATIC_ACK_FIXED_FIELDS_SIZE],
|
// -- start AES-CTR(es_ee) encrypted section (IV is first 12 bytes of SHA384(hk))
|
||||||
// alice_static_blob_length: u16,
|
pub alice_static_blob_length: [u8; 2],
|
||||||
// alice_static_blob: [u8; ???],
|
pub alice_static_blob: [u8; ???],
|
||||||
// alice_metadata_length: u16,
|
pub alice_metadata_length: [u8; 2],
|
||||||
// alice_metadata: [u8; ???],
|
pub alice_metadata: [u8; ???],
|
||||||
// hmac_es_ee_se_hk_psk: [u8; HMAC_SHA384_SIZE],
|
|
||||||
// -- end encrypted section
|
// -- end encrypted section
|
||||||
// pub gcm_mac: [u8; 16],
|
pub hmac_es_ee: [u8; HMAC_SHA384_SIZE],
|
||||||
|
pub hmac_es_ee_se_hk_psk: [u8; HMAC_SHA384_SIZE],
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -86,10 +107,9 @@ pub(crate) struct NoiseXKAliceStaticAck {
|
||||||
pub(crate) struct AliceRekeyInit {
|
pub(crate) struct AliceRekeyInit {
|
||||||
pub header: [u8; HEADER_SIZE],
|
pub header: [u8; HEADER_SIZE],
|
||||||
// -- start AES-GCM encrypted portion (using current key)
|
// -- start AES-GCM encrypted portion (using current key)
|
||||||
pub alice_e: [u8; P384_PUBLIC_KEY_SIZE],
|
pub alice_noise_e: [u8; P384_PUBLIC_KEY_SIZE],
|
||||||
pub alice_hk_public: [u8; KYBER_PUBLICKEYBYTES],
|
|
||||||
// -- end AES-GCM encrypted portion
|
// -- end AES-GCM encrypted portion
|
||||||
pub gcm_mac: [u8; 16],
|
pub gcm_mac: [u8; AES_GCM_TAG_SIZE],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
|
@ -97,21 +117,12 @@ pub(crate) struct AliceRekeyInit {
|
||||||
pub(crate) struct BobRekeyAck {
|
pub(crate) struct BobRekeyAck {
|
||||||
pub header: [u8; HEADER_SIZE],
|
pub header: [u8; HEADER_SIZE],
|
||||||
// -- start AES-GCM encrypted portion (using current key)
|
// -- start AES-GCM encrypted portion (using current key)
|
||||||
pub bob_e: [u8; P384_PUBLIC_KEY_SIZE],
|
pub bob_noise_e: [u8; P384_PUBLIC_KEY_SIZE],
|
||||||
pub bob_hk_ciphertext: [u8; KYBER_CIPHERTEXTBYTES],
|
pub ee_fingerprint: [u8; SHA384_HASH_SIZE],
|
||||||
// -- end AES-GCM encrypted portion
|
// -- end AES-GCM encrypted portion
|
||||||
pub gcm_mac: [u8; 16],
|
pub gcm_mac: [u8; AES_GCM_TAG_SIZE],
|
||||||
}
|
}
|
||||||
|
|
||||||
// Annotate only these structs as being compatible with packet_buffer_as_bytes(). These structs
|
|
||||||
// are packed flat buffers containing only byte or byte array fields, making them safe to treat
|
|
||||||
// this way even on architectures that require type size aligned access.
|
|
||||||
impl ProtocolFlatBuffer for NoiseXKAliceEphemeralOffer {}
|
|
||||||
impl ProtocolFlatBuffer for NoiseXKBobEphemeralCounterOffer {}
|
|
||||||
//impl ProtocolFlatBuffer for NoiseXKAliceStaticAck {}
|
|
||||||
impl ProtocolFlatBuffer for AliceRekeyInit {}
|
|
||||||
impl ProtocolFlatBuffer for BobRekeyAck {}
|
|
||||||
|
|
||||||
/// Assemble a series of fragments into a buffer and return the length of the assembled packet in bytes.
|
/// Assemble a series of fragments into a buffer and return the length of the assembled packet in bytes.
|
||||||
pub(crate) fn assemble_fragments_into<A: ApplicationLayer>(fragments: &[A::IncomingPacketBuffer], d: &mut [u8]) -> Result<usize, Error> {
|
pub(crate) fn assemble_fragments_into<A: ApplicationLayer>(fragments: &[A::IncomingPacketBuffer], d: &mut [u8]) -> Result<usize, Error> {
|
||||||
let mut l = 0;
|
let mut l = 0;
|
||||||
|
@ -133,22 +144,31 @@ pub(crate) fn assemble_fragments_into<A: ApplicationLayer>(fragments: &[A::Incom
|
||||||
return Ok(l);
|
return Ok(l);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Down here is where the only unsafe code here lives. It's instrumented with assertions wherever
|
// Annotate only these structs as being compatible with packet_buffer_as_bytes(). These structs
|
||||||
// possible and just helps us efficiently cast to/from flat buffers.
|
// are packed flat buffers containing only byte or byte array fields, making them safe to treat
|
||||||
|
// this way even on architectures that require type size aligned access.
|
||||||
|
pub(crate) trait ProtocolFlatBuffer {}
|
||||||
|
impl ProtocolFlatBuffer for NoiseXKAliceEphemeralOffer {}
|
||||||
|
impl ProtocolFlatBuffer for NoiseXKBobEphemeralCounterOffer {}
|
||||||
|
impl ProtocolFlatBuffer for BobNoteToSelf {}
|
||||||
|
//impl ProtocolFlatBuffer for NoiseXKAliceStaticAck {}
|
||||||
|
impl ProtocolFlatBuffer for AliceRekeyInit {}
|
||||||
|
impl ProtocolFlatBuffer for BobRekeyAck {}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn new_packet_buffer<B: ProtocolFlatBuffer>() -> B {
|
pub(crate) fn byte_array_as_proto_buffer<B: ProtocolFlatBuffer>(b: &[u8]) -> Result<&B, Error> {
|
||||||
unsafe { std::mem::zeroed() }
|
if b.len() >= size_of::<B>() {
|
||||||
|
Ok(unsafe { &*b.as_ptr().cast() })
|
||||||
|
} else {
|
||||||
|
Err(Error::InvalidPacket)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn packet_buffer_as_bytes<B: ProtocolFlatBuffer>(b: &B) -> &[u8; NOISE_MAX_HANDSHAKE_PACKET_SIZE] {
|
pub(crate) fn byte_array_as_proto_buffer_mut<B: ProtocolFlatBuffer>(b: &mut [u8]) -> Result<&mut B, Error> {
|
||||||
assert_eq!(size_of::<B>(), NOISE_MAX_HANDSHAKE_PACKET_SIZE);
|
if b.len() >= size_of::<B>() {
|
||||||
unsafe { &*(b as *const B).cast() }
|
Ok(unsafe { &mut *b.as_mut_ptr().cast() })
|
||||||
|
} else {
|
||||||
|
Err(Error::InvalidPacket)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub(crate) fn packet_buffer_as_bytes_mut<B: ProtocolFlatBuffer>(b: &mut B) -> &mut [u8; NOISE_MAX_HANDSHAKE_PACKET_SIZE] {
|
|
||||||
assert_eq!(size_of::<B>(), NOISE_MAX_HANDSHAKE_PACKET_SIZE);
|
|
||||||
unsafe { &mut *(b as *mut B).cast() }
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ use crate::constants::SESSION_ID_SIZE;
|
||||||
pub struct SessionId(NonZeroU64); // stored little endian internally
|
pub struct SessionId(NonZeroU64); // stored little endian internally
|
||||||
|
|
||||||
impl SessionId {
|
impl SessionId {
|
||||||
|
pub const NONE: u64 = 0;
|
||||||
pub const MAX: u64 = 0xffffffffffff;
|
pub const MAX: u64 = 0xffffffffffff;
|
||||||
|
|
||||||
/// Create a new session ID, panicing if 'i' is zero or exceeds MAX.
|
/// Create a new session ID, panicing if 'i' is zero or exceeds MAX.
|
||||||
|
|
531
zssp/src/zssp.rs
531
zssp/src/zssp.rs
|
@ -3,24 +3,23 @@
|
||||||
// ZSSP: ZeroTier Secure Session Protocol
|
// ZSSP: ZeroTier Secure Session Protocol
|
||||||
// FIPS compliant Noise_IK with Jedi powers and built-in attack-resistant large payload (fragmentation) support.
|
// FIPS compliant Noise_IK with Jedi powers and built-in attack-resistant large payload (fragmentation) support.
|
||||||
|
|
||||||
use std::io::Write;
|
use std::mem::size_of;
|
||||||
|
use std::num::NonZeroU64;
|
||||||
use std::sync::atomic::{AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
use std::sync::{Mutex, RwLock};
|
use std::sync::{Mutex, RwLock};
|
||||||
|
|
||||||
use pqc_kyber::KYBER_SECRETKEYBYTES;
|
use pqc_kyber::KYBER_SECRETKEYBYTES;
|
||||||
|
|
||||||
use zerotier_crypto::aes::{Aes, AesGcm};
|
use zerotier_crypto::aes::{Aes, AesCtr, AesGcm};
|
||||||
use zerotier_crypto::hash::{hmac_sha512, HMACSHA384, SHA384};
|
use zerotier_crypto::hash::{hmac_sha512, HMACSHA384, HMAC_SHA384_SIZE, SHA384, SHA384_HASH_SIZE};
|
||||||
use zerotier_crypto::p384::{P384KeyPair, P384PublicKey, P384_PUBLIC_KEY_SIZE};
|
use zerotier_crypto::p384::{P384KeyPair, P384PublicKey};
|
||||||
use zerotier_crypto::random;
|
|
||||||
use zerotier_crypto::secret::Secret;
|
use zerotier_crypto::secret::Secret;
|
||||||
use zerotier_crypto::secure_eq;
|
use zerotier_crypto::{random, secure_eq};
|
||||||
|
|
||||||
use zerotier_utils::gatherarray::GatherArray;
|
use zerotier_utils::gatherarray::GatherArray;
|
||||||
use zerotier_utils::memory;
|
use zerotier_utils::memory;
|
||||||
use zerotier_utils::ringbuffermap::RingBufferMap;
|
use zerotier_utils::ringbuffermap::RingBufferMap;
|
||||||
use zerotier_utils::unlikely_branch;
|
use zerotier_utils::unlikely_branch;
|
||||||
use zerotier_utils::varint;
|
|
||||||
|
|
||||||
use crate::applicationlayer::ApplicationLayer;
|
use crate::applicationlayer::ApplicationLayer;
|
||||||
use crate::constants::*;
|
use crate::constants::*;
|
||||||
|
@ -54,7 +53,6 @@ pub enum ReceiveResult<'a, H: ApplicationLayer> {
|
||||||
/// with a single listen socket, local bound port, or other inbound endpoint.
|
/// with a single listen socket, local bound port, or other inbound endpoint.
|
||||||
pub struct ReceiveContext<H: ApplicationLayer> {
|
pub struct ReceiveContext<H: ApplicationLayer> {
|
||||||
initial_offer_defrag: Mutex<RingBufferMap<u64, GatherArray<H::IncomingPacketBuffer, KEY_EXCHANGE_MAX_FRAGMENTS>, 1024, 128>>,
|
initial_offer_defrag: Mutex<RingBufferMap<u64, GatherArray<H::IncomingPacketBuffer, KEY_EXCHANGE_MAX_FRAGMENTS>, 1024, 128>>,
|
||||||
incoming_init_header_check_cipher: Aes,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A FIPS compliant variant of Noise_IK with hybrid Kyber1024 PQ data forward secrecy.
|
/// A FIPS compliant variant of Noise_IK with hybrid Kyber1024 PQ data forward secrecy.
|
||||||
|
@ -65,80 +63,35 @@ pub struct Session<Application: ApplicationLayer> {
|
||||||
/// An arbitrary application defined object associated with each session
|
/// An arbitrary application defined object associated with each session
|
||||||
pub application_data: Application::Data,
|
pub application_data: Application::Data,
|
||||||
|
|
||||||
|
psk: Secret<64>, // External PSK provided by application
|
||||||
send_counter: AtomicU64, // Outgoing packet counter and nonce state
|
send_counter: AtomicU64, // Outgoing packet counter and nonce state
|
||||||
receive_window: [AtomicU64; COUNTER_WINDOW_MAX_OUT_OF_ORDER], // Receive window for anti-replay and deduplication
|
receive_window: [AtomicU64; COUNTER_WINDOW_MAX_OUT_OF_ORDER], // Receive window for anti-replay and deduplication
|
||||||
header_check_cipher: Aes, // Cipher used for header check codes (not Noise related)
|
header_check_cipher: Aes, // Cipher used to protect header (not Noise related)
|
||||||
offer: Mutex<EphemeralOffer>, // Most recently sent ephemeral offer
|
offer: Mutex<EphemeralOffer>, // Most recently sent ephemeral offer
|
||||||
state: RwLock<State>, // Miscellaneous mutable state
|
state: RwLock<State>, // Miscellaneous mutable state
|
||||||
|
|
||||||
//psk: Secret<64>, // Arbitrary PSK provided by external code
|
|
||||||
//noise_ss: Secret<48>, // Static raw shared ECDH NIST P-384 key
|
|
||||||
//state: RwLock<SessionMutableState>, // Mutable parts of state (other than defrag buffers)
|
|
||||||
//remote_s_public_blob_hash: [u8; 48], // SHA384(remote static public key blob)
|
|
||||||
//remote_s_public_p384_bytes: [u8; P384_PUBLIC_KEY_SIZE], // Remote NIST P-384 static public key
|
|
||||||
defrag: Mutex<RingBufferMap<u64, GatherArray<Application::IncomingPacketBuffer, MAX_FRAGMENTS>, 8, 8>>,
|
defrag: Mutex<RingBufferMap<u64, GatherArray<Application::IncomingPacketBuffer, MAX_FRAGMENTS>, 8, 8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum EphemeralOffer {
|
enum EphemeralOffer {
|
||||||
None,
|
None,
|
||||||
Alice(Secret<64>, P384KeyPair, [u8; KYBER_SECRETKEYBYTES]),
|
AliceNoiseXKInit(Secret<64>, P384KeyPair, [u8; KYBER_SECRETKEYBYTES]), // noise_es, alice_noise_e_secret, alice_hk_secret
|
||||||
Bob(Secret<64>, P384KeyPair),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct State {
|
struct State {
|
||||||
keys: [Option<SessionKey>; 2],
|
keys: [Option<SessionKey>; 2],
|
||||||
current_key: usize,
|
current_key: usize,
|
||||||
psk: Secret<64>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
struct SessionMutableState {
|
|
||||||
remote_session_id: Option<SessionId>, // The other side's 48-bit session ID
|
|
||||||
session_keys: [Option<SessionKey>; 2], // Buffers to store last and latest key by 1-bit key index
|
|
||||||
cur_session_key_idx: usize, // Pointer to latest session key other side is confirmed to have
|
|
||||||
offer: Option<EphemeralOffer>, // Most recent ephemeral offer sent to remote
|
|
||||||
last_remote_offer: i64, // Time of most recent ephemeral offer (ms)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/// A shared symmetric session key.
|
|
||||||
struct SessionKey {
|
struct SessionKey {
|
||||||
ratchet_count: u64, // Number of preceding session keys in ratchet
|
|
||||||
rekey_at_time: i64, // Rekey at or after this time (ticks)
|
|
||||||
rekey_at_counter: u64, // Rekey at or after this counter
|
|
||||||
expire_at_counter: u64, // Hard error when this counter value is reached or exceeded
|
|
||||||
secret_fingerprint: [u8; 16], // First 128 bits of a SHA384 computed from the secret
|
|
||||||
ratchet_key: Secret<64>, // Ratchet key for deriving the next session key
|
|
||||||
receive_key: Secret<AES_KEY_SIZE>, // Receive side AES-GCM key
|
receive_key: Secret<AES_KEY_SIZE>, // Receive side AES-GCM key
|
||||||
send_key: Secret<AES_KEY_SIZE>, // Send side AES-GCM key
|
send_key: Secret<AES_KEY_SIZE>, // Send side AES-GCM key
|
||||||
receive_cipher_pool: Mutex<Vec<Box<AesGcm>>>, // Pool of reusable sending ciphers
|
receive_cipher_pool: Mutex<Vec<Box<AesGcm>>>, // Pool of reusable sending ciphers
|
||||||
send_cipher_pool: Mutex<Vec<Box<AesGcm>>>, // Pool of reusable receiving ciphers
|
send_cipher_pool: Mutex<Vec<Box<AesGcm>>>, // Pool of reusable receiving ciphers
|
||||||
role: Role, // Was this side Alice or Bob?
|
rekey_at_time: i64, // Rekey at or after this time (ticks)
|
||||||
|
rekey_at_counter: u64, // Rekey at or after this counter
|
||||||
|
expire_at_counter: u64, // Hard error when this counter value is reached or exceeded
|
||||||
confirmed: bool, // We have confirmed that the other side has this key
|
confirmed: bool, // We have confirmed that the other side has this key
|
||||||
jedi: bool, // True if Kyber1024 was used (both sides enabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
/// Alice's KEY_OFFER, remembered so Noise agreement process can resume on KEY_COUNTER_OFFER.
|
|
||||||
struct EphemeralOffer {
|
|
||||||
id: [u8; 16], // Arbitrary random offer ID
|
|
||||||
creation_time: i64, // Local time when offer was created
|
|
||||||
ratchet_count: u64, // Ratchet count (starting at zero) for initial offer
|
|
||||||
ratchet_key: Option<Secret<64>>, // Ratchet key from previous offer or None if first offer
|
|
||||||
ss_key: Secret<64>, // Noise session key "under construction" at state after offer sent
|
|
||||||
alice_e_keypair: P384KeyPair, // NIST P-384 key pair (Noise ephemeral key for Alice)
|
|
||||||
alice_hk_keypair: Option<pqc_kyber::Keypair>, // Kyber1024 key pair (PQ hybrid ephemeral key for Alice)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/// Was this side the one who sent the first offer (Alice) or countered (Bob).
|
|
||||||
///
|
|
||||||
/// Note that the role can switch through the course of a session. It's the side that most recently
|
|
||||||
/// initiated a session or a rekey event. Initiator is Alice, responder is Bob.
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub enum Role {
|
|
||||||
Alice,
|
|
||||||
Bob,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Application: ApplicationLayer> Session<Application> {
|
impl<Application: ApplicationLayer> Session<Application> {
|
||||||
|
@ -355,6 +308,12 @@ impl<Application: ApplicationLayer> Session<Application> {
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the next outgoing counter value.
|
||||||
|
#[inline(always)]
|
||||||
|
fn get_next_outgoing_counter(&self) -> Option<NonZeroU64> {
|
||||||
|
NonZeroU64::new(self.send_counter.fetch_add(1, Ordering::SeqCst))
|
||||||
|
}
|
||||||
|
|
||||||
/// Check the receive window without mutating state.
|
/// Check the receive window without mutating state.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn check_receive_window(&self, counter: u64) -> bool {
|
fn check_receive_window(&self, counter: u64) -> bool {
|
||||||
|
@ -372,12 +331,9 @@ impl<Application: ApplicationLayer> Session<Application> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
||||||
pub fn new(app: &Application) -> Self {
|
pub fn new(_: &Application) -> Self {
|
||||||
Self {
|
Self {
|
||||||
initial_offer_defrag: Mutex::new(RingBufferMap::new(random::next_u32_secure())),
|
initial_offer_defrag: Mutex::new(RingBufferMap::new(random::next_u32_secure())),
|
||||||
incoming_init_header_check_cipher: Aes::new(
|
|
||||||
kbkdf512(app.get_local_s_public_blob_hash(), KBKDF_KEY_USAGE_LABEL_HEADER_CHECK).first_n::<HEADER_CHECK_AES_KEY_SIZE>(),
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -467,10 +423,8 @@ impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
||||||
return Err(Error::UnknownLocalSessionId(local_session_id));
|
return Err(Error::UnknownLocalSessionId(local_session_id));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
unlikely_branch(); // we want data receive to be the priority branch, this is only occasionally used
|
unlikely_branch(); // the data path should always be the 'hot' branch
|
||||||
|
|
||||||
self.incoming_init_header_check_cipher
|
|
||||||
.decrypt_block_in_place(&mut incoming_packet[HEADER_CHECK_ENCRYPT_START..HEADER_CHECK_ENCRYPT_END]);
|
|
||||||
let raw_header_a = u16::from_le(memory::load_raw(&incoming_packet[6..]));
|
let raw_header_a = u16::from_le(memory::load_raw(&incoming_packet[6..]));
|
||||||
let key_index = (raw_header_a & 1) as usize;
|
let key_index = (raw_header_a & 1) as usize;
|
||||||
let packet_type = (raw_header_a.wrapping_shr(1) & 7) as u8;
|
let packet_type = (raw_header_a.wrapping_shr(1) & 7) as u8;
|
||||||
|
@ -625,153 +579,389 @@ impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
||||||
} else {
|
} else {
|
||||||
unlikely_branch();
|
unlikely_branch();
|
||||||
|
|
||||||
|
// For KEX packets go ahead and pre-assemble all fragments to simplify the code below.
|
||||||
|
let mut pkt_assembly_buffer = [0u8; NOISE_MAX_HANDSHAKE_PACKET_SIZE];
|
||||||
|
let pkt_assembled_size = assemble_fragments_into::<Application>(fragments, &mut pkt_assembly_buffer)?;
|
||||||
|
if pkt_assembled_size < MIN_PACKET_SIZE {
|
||||||
|
return Err(Error::InvalidPacket);
|
||||||
|
}
|
||||||
|
let pkt_assembled = &mut pkt_assembly_buffer[..pkt_assembled_size];
|
||||||
|
if pkt_assembled[HEADER_SIZE] != SESSION_PROTOCOL_VERSION {
|
||||||
|
return Err(Error::UnknownProtocolVersion);
|
||||||
|
}
|
||||||
|
|
||||||
match packet_type {
|
match packet_type {
|
||||||
PACKET_TYPE_ALICE_EPHEMERAL_OFFER => {
|
PACKET_TYPE_ALICE_EPHEMERAL_OFFER => {
|
||||||
// Alice (remote) --> Bob (local)
|
// Alice (remote) --> Bob (local)
|
||||||
|
|
||||||
let mut pkt: NoiseXKAliceEphemeralOffer = new_packet_buffer();
|
/*
|
||||||
if assemble_fragments_into::<Application>(fragments, packet_buffer_as_bytes_mut(&mut pkt))?
|
* This is the first message Bob receives from Alice, the initiator. It contains
|
||||||
!= NOISE_XK_ALICE_EPHEMERAL_OFFER_SIZE
|
* Alice's ephemeral keys but not her identity. Bob responds with his ephemeral
|
||||||
{
|
* keys. An opaque sealed object called Bob's "note to self" is also sent. Alice
|
||||||
return Err(Error::InvalidPacket);
|
* must return it with her final key exchange message. It contains Bob's state
|
||||||
}
|
* for the exchange to this point (encrypted and authenticated) so that Bob does
|
||||||
if pkt.session_protocol_version != SESSION_PROTOCOL_VERSION {
|
* not need to allocate any memory or mutate local state until Alice's identity
|
||||||
return Err(Error::UnknownProtocolVersion);
|
* is known and confirmed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// There shouldn't be a session yet on Bob's end.
|
||||||
|
if session.is_some() {
|
||||||
|
return Ok(ReceiveResult::Ignored);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let pkt: &NoiseXKAliceEphemeralOffer = byte_array_as_proto_buffer(pkt_assembled)?;
|
||||||
|
|
||||||
let alice_noise_e = P384PublicKey::from_bytes(&pkt.alice_noise_e).ok_or(Error::FailedAuthentication)?;
|
let alice_noise_e = P384PublicKey::from_bytes(&pkt.alice_noise_e).ok_or(Error::FailedAuthentication)?;
|
||||||
let noise_es = app.get_local_s_keypair().agree(&alice_noise_e).ok_or(Error::FailedAuthentication)?;
|
let noise_es = app.get_local_s_keypair().agree(&alice_noise_e).ok_or(Error::FailedAuthentication)?;
|
||||||
|
|
||||||
let mut gcm = AesGcm::new(
|
// Authenticate packet and prove that Alice knows our identity.
|
||||||
&kbkdf512(noise_es.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_PAYLOAD_ENCRYPTION).as_bytes()[..AES_KEY_SIZE],
|
if !secure_eq(
|
||||||
false,
|
&pkt.hmac_es,
|
||||||
);
|
&hmac_sha384_2(
|
||||||
gcm.reset_init_gcm(&SHA384::hash(&pkt.alice_noise_e)[..AES_GCM_NONCE_SIZE]);
|
&kbkdf512(noise_es.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION).as_bytes()[..HMAC_SHA384_SIZE],
|
||||||
gcm.crypt_in_place(
|
&message_nonce,
|
||||||
&mut packet_buffer_as_bytes_mut(&mut pkt)
|
&pkt_assembled[HEADER_SIZE..NoiseXKAliceEphemeralOffer::AUTH_START],
|
||||||
[NOISE_XK_ALICE_EPHEMERAL_OFFER_ENCRYPTED_SECTION_START..NOISE_XK_ALICE_EPHEMERAL_OFFER_ENCRYPTED_SECTION_END],
|
),
|
||||||
);
|
) {
|
||||||
if !gcm.finish_decrypt(&pkt.gcm_mac) {
|
|
||||||
return Err(Error::FailedAuthentication);
|
return Err(Error::FailedAuthentication);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Decrypt encrypted part of payload.
|
||||||
|
let mut ctr = AesCtr::new(
|
||||||
|
&kbkdf512(noise_es.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_PAYLOAD_ENCRYPTION).as_bytes()[..AES_KEY_SIZE],
|
||||||
|
);
|
||||||
|
ctr.reset_set_iv(&SHA384::hash(&pkt.alice_noise_e)[..AES_CTR_NONCE_SIZE]);
|
||||||
|
ctr.crypt_in_place(&mut pkt_assembled[NoiseXKAliceEphemeralOffer::ENC_START..NoiseXKAliceEphemeralOffer::AUTH_START]);
|
||||||
|
let pkt: &NoiseXKAliceEphemeralOffer = byte_array_as_proto_buffer(pkt_assembled)?;
|
||||||
|
|
||||||
let alice_session_id = SessionId::new_from_bytes(&pkt.alice_session_id).ok_or(Error::InvalidPacket)?;
|
let alice_session_id = SessionId::new_from_bytes(&pkt.alice_session_id).ok_or(Error::InvalidPacket)?;
|
||||||
|
|
||||||
|
// Create Bob's ephemeral keys and derive noise_es_ee.
|
||||||
let (bob_hk_ciphertext, hk) = pqc_kyber::encapsulate(&pkt.alice_hk_public, &mut random::SecureRandom::default())
|
let (bob_hk_ciphertext, hk) = pqc_kyber::encapsulate(&pkt.alice_hk_public, &mut random::SecureRandom::default())
|
||||||
.map_err(|_| Error::FailedAuthentication)?;
|
.map_err(|_| Error::FailedAuthentication)?;
|
||||||
|
let bob_noise_e_secret = P384KeyPair::generate();
|
||||||
let (bob_session_id, application_data) = app.new_session(remote_address).ok_or(Error::NewSessionRejected)?;
|
|
||||||
let bob_e_secret = P384KeyPair::generate();
|
|
||||||
|
|
||||||
let noise_es_ee = Secret(hmac_sha512(
|
let noise_es_ee = Secret(hmac_sha512(
|
||||||
noise_es.as_bytes(),
|
noise_es.as_bytes(),
|
||||||
bob_e_secret.agree(&alice_noise_e).ok_or(Error::FailedAuthentication)?.as_bytes(),
|
bob_noise_e_secret
|
||||||
|
.agree(&alice_noise_e)
|
||||||
|
.ok_or(Error::FailedAuthentication)?
|
||||||
|
.as_bytes(),
|
||||||
));
|
));
|
||||||
|
|
||||||
let mut reply: NoiseXKBobEphemeralCounterOffer = new_packet_buffer();
|
// Create Bob's ephemeral counter-offer reply.
|
||||||
|
let mut reply_buffer = [0u8; NOISE_MAX_HANDSHAKE_PACKET_SIZE];
|
||||||
|
let reply: &mut NoiseXKBobEphemeralCounterOffer = byte_array_as_proto_buffer_mut(&mut reply_buffer)?;
|
||||||
reply.session_protocol_version = SESSION_PROTOCOL_VERSION;
|
reply.session_protocol_version = SESSION_PROTOCOL_VERSION;
|
||||||
reply.bob_noise_e = bob_e_secret.public_key_bytes().clone();
|
reply.bob_noise_e = bob_noise_e_secret.public_key_bytes().clone();
|
||||||
reply.bob_session_id = bob_session_id.as_bytes().clone();
|
|
||||||
reply.bob_hk_ciphertext = bob_hk_ciphertext;
|
reply.bob_hk_ciphertext = bob_hk_ciphertext;
|
||||||
|
|
||||||
let mut gcm = AesGcm::new(
|
// Create, encrypt, and tag Bob's opaque "note to self" that Alice must return.
|
||||||
&kbkdf512(noise_es_ee.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_PAYLOAD_ENCRYPTION).as_bytes()[..AES_KEY_SIZE],
|
let bob_note_to_self: &mut BobNoteToSelf = byte_array_as_proto_buffer_mut(&mut reply.bob_note_to_self)?;
|
||||||
true,
|
bob_note_to_self.iv = random::get_bytes_secure();
|
||||||
);
|
bob_note_to_self.timestamp = current_time.to_le_bytes();
|
||||||
gcm.reset_init_gcm(&SHA384::hash(bob_e_secret.public_key_bytes())[..AES_GCM_NONCE_SIZE]);
|
bob_note_to_self.alice_session_id = *alice_session_id.as_bytes();
|
||||||
gcm.crypt_in_place(
|
bob_note_to_self.bob_noise_e = *bob_noise_e_secret.public_key_bytes();
|
||||||
&mut packet_buffer_as_bytes_mut(&mut reply)[NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_ENCRYPTED_SECTION_START
|
bob_note_to_self.bob_noise_e_secret = *bob_noise_e_secret.secret_key_bytes().as_bytes();
|
||||||
..NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_ENCRYPTED_SECTION_END],
|
bob_note_to_self.hk = hk;
|
||||||
);
|
bob_note_to_self.noise_es_ee = *noise_es_ee.as_bytes();
|
||||||
reply.gcm_mac = gcm.finish_encrypt();
|
let mut gcm = get_note_to_self_cipher(true, &bob_note_to_self.iv);
|
||||||
|
gcm.crypt_in_place(&mut reply.bob_note_to_self[BobNoteToSelf::ENC_START..BobNoteToSelf::AUTH_START]);
|
||||||
|
reply.bob_note_to_self[BobNoteToSelf::AUTH_START..].copy_from_slice(&gcm.finish_encrypt());
|
||||||
|
|
||||||
|
// Encrypt encrypted part of reply packet.
|
||||||
|
let mut ctr = AesCtr::new(
|
||||||
|
&kbkdf512(noise_es_ee.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_PAYLOAD_ENCRYPTION).as_bytes()[..AES_KEY_SIZE],
|
||||||
|
);
|
||||||
|
ctr.reset_set_iv(&SHA384::hash(bob_noise_e_secret.public_key_bytes())[..AES_CTR_NONCE_SIZE]);
|
||||||
|
ctr.crypt_in_place(
|
||||||
|
&mut reply_buffer[NoiseXKBobEphemeralCounterOffer::ENC_START..NoiseXKBobEphemeralCounterOffer::AUTH_START],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add HMAC-SHA384 to reply packet, allowing Alice to derive noise_es_ee and authenticate.
|
||||||
|
let reply_hmac = hmac_sha384_2(
|
||||||
|
&kbkdf512(noise_es_ee.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION).as_bytes()[..HMAC_SHA384_SIZE],
|
||||||
|
&create_message_nonce(PACKET_TYPE_BOB_EPHEMERAL_COUNTER_OFFER, 1),
|
||||||
|
&reply_buffer[HEADER_SIZE..NoiseXKBobEphemeralCounterOffer::AUTH_START],
|
||||||
|
);
|
||||||
|
reply_buffer[NoiseXKBobEphemeralCounterOffer::AUTH_START..].copy_from_slice(&reply_hmac);
|
||||||
|
|
||||||
|
// This sub-key is used to protect the framing/fragmentation protocol, not Noise related.
|
||||||
let header_check_cipher = Aes::new(
|
let header_check_cipher = Aes::new(
|
||||||
&kbkdf512(noise_es.as_bytes(), KBKDF_KEY_USAGE_LABEL_HEADER_CHECK).as_bytes()[..AES_HEADER_CHECK_KEY_SIZE],
|
&kbkdf512(noise_es_ee.as_bytes(), KBKDF_KEY_USAGE_LABEL_HEADER_CHECK).as_bytes()[..AES_HEADER_CHECK_KEY_SIZE],
|
||||||
);
|
);
|
||||||
|
|
||||||
send_with_fragmentation(
|
send_with_fragmentation(
|
||||||
send,
|
send,
|
||||||
&mut packet_buffer_as_bytes_mut(&mut reply)[..NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_SIZE],
|
&mut reply_buffer[..size_of::<NoiseXKBobEphemeralCounterOffer>()],
|
||||||
mtu,
|
mtu,
|
||||||
PACKET_TYPE_BOB_EPHEMERAL_COUNTER_OFFER,
|
PACKET_TYPE_BOB_EPHEMERAL_COUNTER_OFFER,
|
||||||
u64::from(alice_session_id),
|
Some(alice_session_id),
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
&header_check_cipher,
|
Some(&header_check_cipher),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
return Ok(ReceiveResult::OkNewSession(Session {
|
return Ok(ReceiveResult::Ok);
|
||||||
id: bob_session_id,
|
|
||||||
application_data,
|
|
||||||
send_counter: AtomicU64::new(2), // 1 was used in reply
|
|
||||||
receive_window: std::array::from_fn(|_| AtomicU64::new(0)),
|
|
||||||
header_check_cipher,
|
|
||||||
offer: Mutex::new(EphemeralOffer::Bob(noise_es_ee, bob_e_secret)),
|
|
||||||
state: RwLock::new(State { keys: [None, None], current_key: 0, psk: Secret::default() }),
|
|
||||||
defrag: Mutex::new(RingBufferMap::new(random::xorshift64_random() as u32)),
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PACKET_TYPE_BOB_EPHEMERAL_COUNTER_OFFER => {
|
PACKET_TYPE_BOB_EPHEMERAL_COUNTER_OFFER => {
|
||||||
// Bob (remote) --> Alice (local)
|
// Bob (remote) --> Alice (local)
|
||||||
|
|
||||||
if let Some(session) = session {
|
/*
|
||||||
let mut pkt: NoiseXKBobEphemeralCounterOffer = new_packet_buffer();
|
* This is Bob's reply to Alice's first message, allowing Alice to verify Bob's
|
||||||
if assemble_fragments_into::<Application>(fragments, packet_buffer_as_bytes_mut(&mut pkt))?
|
* identity. Once this is done Alice can send her identity (encrypted) to complete
|
||||||
!= NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_SIZE
|
* the negotiation.
|
||||||
{
|
*/
|
||||||
return Err(Error::InvalidPacket);
|
|
||||||
}
|
|
||||||
if pkt.session_protocol_version != SESSION_PROTOCOL_VERSION {
|
|
||||||
return Err(Error::UnknownProtocolVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if let Some(session) = session {
|
||||||
let mut offer = session.offer.lock().unwrap();
|
let mut offer = session.offer.lock().unwrap();
|
||||||
match &*offer {
|
match &*offer {
|
||||||
EphemeralOffer::Alice(noise_es, alice_e_secret, alice_hk_secret) => {
|
EphemeralOffer::AliceNoiseXKInit(noise_es, alice_e_secret, alice_hk_secret) => {
|
||||||
|
let pkt: &NoiseXKBobEphemeralCounterOffer = byte_array_as_proto_buffer(pkt_assembled)?;
|
||||||
|
|
||||||
|
// Derive noise_es_ee from Bob's ephemeral public key.
|
||||||
let bob_noise_e = P384PublicKey::from_bytes(&pkt.bob_noise_e).ok_or(Error::FailedAuthentication)?;
|
let bob_noise_e = P384PublicKey::from_bytes(&pkt.bob_noise_e).ok_or(Error::FailedAuthentication)?;
|
||||||
let noise_es_ee = Secret(hmac_sha512(
|
let noise_es_ee = Secret(hmac_sha512(
|
||||||
noise_es.as_bytes(),
|
noise_es.as_bytes(),
|
||||||
alice_e_secret.agree(&bob_noise_e).ok_or(Error::FailedAuthentication)?.as_bytes(),
|
alice_e_secret.agree(&bob_noise_e).ok_or(Error::FailedAuthentication)?.as_bytes(),
|
||||||
));
|
));
|
||||||
let noise_es_ee_se = app.get_local_s_keypair().agree(&bob_noise_e).ok_or(Error::FailedAuthentication)?;
|
|
||||||
|
|
||||||
let mut gcm = AesGcm::new(
|
let noise_es_ee_kex_key = kbkdf512(noise_es_ee.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_PAYLOAD_ENCRYPTION);
|
||||||
&kbkdf512(noise_es_ee.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_PAYLOAD_ENCRYPTION).as_bytes()
|
let noise_es_ee_kex_hmac_key = kbkdf512(noise_es_ee.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION);
|
||||||
[..AES_KEY_SIZE],
|
|
||||||
false,
|
// Authenticate Bob's reply and the validity of bob_noise_e.
|
||||||
);
|
if !secure_eq(
|
||||||
gcm.reset_init_gcm(&SHA384::hash(&pkt.bob_noise_e)[..AES_GCM_NONCE_SIZE]);
|
&pkt.hmac_es_ee,
|
||||||
gcm.crypt_in_place(
|
&hmac_sha384_2(
|
||||||
&mut packet_buffer_as_bytes_mut(&mut pkt)[NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_ENCRYPTED_SECTION_START
|
&noise_es_ee_kex_hmac_key.as_bytes()[..HMAC_SHA384_SIZE],
|
||||||
..NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_ENCRYPTED_SECTION_END],
|
&message_nonce,
|
||||||
);
|
&pkt_assembled[HEADER_SIZE..NoiseXKAliceEphemeralOffer::AUTH_START],
|
||||||
if !gcm.finish_decrypt(&pkt.gcm_mac) {
|
),
|
||||||
|
) {
|
||||||
return Err(Error::FailedAuthentication);
|
return Err(Error::FailedAuthentication);
|
||||||
}
|
}
|
||||||
|
|
||||||
let bob_session_id = SessionId::new_from_bytes(&pkt.bob_session_id).ok_or(Error::InvalidPacket)?;
|
// Decrypt encrypted portion after authentication.
|
||||||
|
let mut ctr = AesCtr::new(&noise_es_ee_kex_key.as_bytes()[..AES_KEY_SIZE]);
|
||||||
|
ctr.reset_set_iv(&SHA384::hash(&pkt.bob_noise_e)[..AES_CTR_NONCE_SIZE]);
|
||||||
|
ctr.crypt_in_place(
|
||||||
|
&mut pkt_assembled
|
||||||
|
[NoiseXKBobEphemeralCounterOffer::ENC_START..NoiseXKBobEphemeralCounterOffer::AUTH_START],
|
||||||
|
);
|
||||||
|
let pkt: &NoiseXKBobEphemeralCounterOffer = byte_array_as_proto_buffer(pkt_assembled)?;
|
||||||
|
|
||||||
|
// Noise_XKpsk3 specifies mixing a PSK last. The kyber1024 hybrid key is mixed into
|
||||||
|
// the static PSK and then this is treated as the PSK as far as Noise is concerned.
|
||||||
let hk = pqc_kyber::decapsulate(&pkt.bob_hk_ciphertext, alice_hk_secret)
|
let hk = pqc_kyber::decapsulate(&pkt.bob_hk_ciphertext, alice_hk_secret)
|
||||||
.map_err(|_| Error::FailedAuthentication)?;
|
.map_err(|_| Error::FailedAuthentication)?;
|
||||||
|
let noise_es_ee_se_hk_psk = Secret(hmac_sha512(
|
||||||
|
&hmac_sha512(
|
||||||
|
noise_es_ee.as_bytes(),
|
||||||
|
app.get_local_s_keypair()
|
||||||
|
.agree(&bob_noise_e)
|
||||||
|
.ok_or(Error::FailedAuthentication)?
|
||||||
|
.as_bytes(),
|
||||||
|
),
|
||||||
|
&hmac_sha512(session.psk.as_bytes(), &hk),
|
||||||
|
));
|
||||||
|
|
||||||
|
let reply_counter = session.get_next_outgoing_counter().ok_or(Error::MaxKeyLifetimeExceeded)?;
|
||||||
|
let reply_message_nonce = create_message_nonce(PACKET_TYPE_ALICE_STATIC_ACK, reply_counter.get());
|
||||||
|
|
||||||
|
// Create reply informing Bob of our static identity now that we've verified Bob and set
|
||||||
|
// up forward secrecy. Also return Bob's opaque note.
|
||||||
|
let mut reply_buffer = [0u8; NOISE_MAX_HANDSHAKE_PACKET_SIZE];
|
||||||
|
reply_buffer[HEADER_SIZE] = SESSION_PROTOCOL_VERSION;
|
||||||
|
let mut reply_len = HEADER_SIZE + 1;
|
||||||
|
let mut reply_buffer_append = |b: &[u8]| {
|
||||||
|
let reply_len_new = reply_len + b.len();
|
||||||
|
reply_buffer[reply_len..reply_len_new].copy_from_slice(b);
|
||||||
|
reply_len = reply_len_new;
|
||||||
|
};
|
||||||
|
reply_buffer_append(&pkt.bob_note_to_self);
|
||||||
let alice_s_public_blob = app.get_local_s_public_blob();
|
let alice_s_public_blob = app.get_local_s_public_blob();
|
||||||
|
|
||||||
let mut reply = [0u8; NOISE_MAX_HANDSHAKE_PACKET_SIZE];
|
|
||||||
let mut reply_w = &mut reply[..];
|
|
||||||
assert!(alice_s_public_blob.len() <= (u16::MAX as usize));
|
assert!(alice_s_public_blob.len() <= (u16::MAX as usize));
|
||||||
let _ = reply_w.write_all(&(alice_s_public_blob.len() as u16).to_le_bytes());
|
reply_buffer_append(&(alice_s_public_blob.len() as u16).to_le_bytes());
|
||||||
let _ = reply_w.write_all(alice_s_public_blob);
|
reply_buffer_append(alice_s_public_blob);
|
||||||
let _ = reply_w.write_all(&[0u8, 0u8]); // zero size meta-data, to be implemented later
|
reply_buffer_append(&[0u8, 0u8]); // no meta-data
|
||||||
}
|
|
||||||
_ => {
|
// Encrypt Alice's static identity and other inner payload items.
|
||||||
return Ok(ReceiveResult::Ignored);
|
let mut ctr = AesCtr::new(&noise_es_ee_kex_key.as_bytes()[..AES_KEY_SIZE]);
|
||||||
|
ctr.reset_set_iv(&SHA384::hash(&hk)[..AES_CTR_NONCE_SIZE]);
|
||||||
|
ctr.crypt_in_place(&mut reply_buffer[NOISE_XK_ALICE_STATIC_ACK_ENCRYPTED_SECTION_START..reply_len]);
|
||||||
|
|
||||||
|
// First attach HMAC allowing Bob to verify that this is from the same Alice and to
|
||||||
|
// verify the authenticity of encrypted data.
|
||||||
|
let hmac_es_ee = hmac_sha384_2(
|
||||||
|
&noise_es_ee_kex_hmac_key.as_bytes()[..HMAC_SHA384_SIZE],
|
||||||
|
&reply_message_nonce,
|
||||||
|
&reply_buffer[HEADER_SIZE..reply_len],
|
||||||
|
);
|
||||||
|
reply_buffer[reply_len..reply_len + HMAC_SHA384_SIZE].copy_from_slice(&hmac_es_ee);
|
||||||
|
reply_len += HMAC_SHA384_SIZE;
|
||||||
|
|
||||||
|
// Then attach the final HMAC permitting Bob to verify the authenticity of the whole
|
||||||
|
// key exchange.
|
||||||
|
let hmac_es_ee_se_hk_psk = hmac_sha384_2(
|
||||||
|
&kbkdf512(noise_es_ee_se_hk_psk.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION).as_bytes()
|
||||||
|
[..HMAC_SHA384_SIZE],
|
||||||
|
&reply_message_nonce,
|
||||||
|
&reply_buffer[HEADER_SIZE..reply_len],
|
||||||
|
);
|
||||||
|
reply_buffer[reply_len..reply_len + HMAC_SHA384_SIZE].copy_from_slice(&hmac_es_ee_se_hk_psk);
|
||||||
|
reply_len += HMAC_SHA384_SIZE;
|
||||||
|
|
||||||
|
send_with_fragmentation(
|
||||||
|
send,
|
||||||
|
&mut reply_buffer[..reply_len],
|
||||||
|
mtu,
|
||||||
|
PACKET_TYPE_ALICE_STATIC_ACK,
|
||||||
|
None,
|
||||||
|
0,
|
||||||
|
reply_counter.get(),
|
||||||
|
None,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
*offer = EphemeralOffer::None;
|
||||||
|
|
||||||
|
return Ok(ReceiveResult::Ok);
|
||||||
}
|
}
|
||||||
|
_ => return Ok(ReceiveResult::Ignored),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::SessionNotEstablished);
|
return Err(Error::SessionNotEstablished);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PACKET_TYPE_ALICE_STATIC_ACK => {}
|
PACKET_TYPE_ALICE_STATIC_ACK => {
|
||||||
|
// Alice (remote) --> Bob (local)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* After negotiating a keyed session and Alice has had the opportunity to
|
||||||
|
* verify Bob, this is when Bob gets to learn who Alice is. At this point
|
||||||
|
* Bob can make a final decision about whether to keep talking to Alice
|
||||||
|
* and can create an actual session using the state memo-ized in the memo
|
||||||
|
* that Alice must return.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// There shouldn't be a session yet on Bob's end.
|
||||||
|
if session.is_some() {
|
||||||
|
return Ok(ReceiveResult::Ignored);
|
||||||
|
}
|
||||||
|
|
||||||
|
if pkt_assembled.len() < NOISE_XK_ALICE_STATIC_ACK_MIN_SIZE {
|
||||||
|
return Err(Error::InvalidPacket);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt and authenticate our "note to self."
|
||||||
|
let mut bob_note_to_self_buffer = Secret([0u8; size_of::<BobNoteToSelf>()]);
|
||||||
|
let mut gcm = get_note_to_self_cipher(
|
||||||
|
false,
|
||||||
|
&pkt_assembled
|
||||||
|
[NOISE_XK_ALICE_STATIC_ACK_BOB_NOTE_TO_SELF_START..NOISE_XK_ALICE_STATIC_ACK_BOB_NOTE_TO_SELF_START + 16],
|
||||||
|
);
|
||||||
|
gcm.crypt(
|
||||||
|
&pkt_assembled[NOISE_XK_ALICE_STATIC_ACK_BOB_NOTE_TO_SELF_START + 16
|
||||||
|
..NOISE_XK_ALICE_STATIC_ACK_BOB_NOTE_TO_SELF_END - AES_GCM_TAG_SIZE],
|
||||||
|
&mut bob_note_to_self_buffer.as_bytes_mut()[BobNoteToSelf::ENC_START..BobNoteToSelf::AUTH_START],
|
||||||
|
);
|
||||||
|
if !gcm.finish_decrypt(
|
||||||
|
&pkt_assembled[NOISE_XK_ALICE_STATIC_ACK_BOB_NOTE_TO_SELF_END - AES_GCM_TAG_SIZE
|
||||||
|
..NOISE_XK_ALICE_STATIC_ACK_BOB_NOTE_TO_SELF_END],
|
||||||
|
) {
|
||||||
|
return Err(Error::FailedAuthentication);
|
||||||
|
}
|
||||||
|
let bob_note_to_self: &BobNoteToSelf = byte_array_as_proto_buffer(bob_note_to_self_buffer.as_bytes())?;
|
||||||
|
if (current_time - i64::from_le_bytes(bob_note_to_self.timestamp)) >= BOB_NOTE_TO_SELF_TIMEOUT_MS {
|
||||||
|
return Err(Error::FailedAuthentication);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore state from when Alice first tried to contact us.
|
||||||
|
let alice_session_id = SessionId::new_from_bytes(&bob_note_to_self.alice_session_id).ok_or(Error::InvalidPacket)?;
|
||||||
|
let bob_noise_e_secret = P384KeyPair::from_bytes(&bob_note_to_self.bob_noise_e, &bob_note_to_self.bob_noise_e_secret)
|
||||||
|
.ok_or(Error::InvalidPacket)?;
|
||||||
|
let hk = Secret(bob_note_to_self.hk);
|
||||||
|
let noise_es_ee = Secret(bob_note_to_self.noise_es_ee);
|
||||||
|
drop(bob_note_to_self_buffer);
|
||||||
|
|
||||||
|
let pkt_assembled_enc_end = pkt_assembled.len() - (HMAC_SHA384_SIZE * 2);
|
||||||
|
|
||||||
|
// Authenticate packet with noise_es_ee (first HMAC) before decrypting and parsing static info.
|
||||||
|
if !secure_eq(
|
||||||
|
&pkt_assembled[pkt_assembled_enc_end..pkt_assembled.len() - HMAC_SHA384_SIZE],
|
||||||
|
&hmac_sha384_2(
|
||||||
|
&kbkdf512(noise_es_ee.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION).as_bytes()[..HMAC_SHA384_SIZE],
|
||||||
|
&message_nonce,
|
||||||
|
&pkt_assembled[HEADER_SIZE..pkt_assembled_enc_end],
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
return Err(Error::FailedAuthentication);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut pkt_saved_for_final_hmac = [0u8; NOISE_MAX_HANDSHAKE_PACKET_SIZE];
|
||||||
|
pkt_saved_for_final_hmac[..pkt_assembled.len()].copy_from_slice(pkt_assembled);
|
||||||
|
|
||||||
|
// Decrypt Alice's static identity and decode.
|
||||||
|
let mut ctr = AesCtr::new(
|
||||||
|
&kbkdf512(noise_es_ee.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_PAYLOAD_ENCRYPTION).as_bytes()[..AES_KEY_SIZE],
|
||||||
|
);
|
||||||
|
ctr.reset_set_iv(&SHA384::hash(hk.as_bytes())[..AES_CTR_NONCE_SIZE]);
|
||||||
|
ctr.crypt_in_place(&mut pkt_assembled[NOISE_XK_ALICE_STATIC_ACK_ENCRYPTED_SECTION_START..pkt_assembled_enc_end]);
|
||||||
|
|
||||||
|
let mut alice_static_info = &pkt_assembled[NOISE_XK_ALICE_STATIC_ACK_ENCRYPTED_SECTION_START..pkt_assembled_enc_end];
|
||||||
|
if alice_static_info.len() < 2 {
|
||||||
|
return Err(Error::InvalidPacket);
|
||||||
|
}
|
||||||
|
let alice_static_public_blob_len = u16::from_le_bytes(alice_static_info[..2].try_into().unwrap()) as usize;
|
||||||
|
alice_static_info = &alice_static_info[2..];
|
||||||
|
if alice_static_info.len() < alice_static_public_blob_len {
|
||||||
|
return Err(Error::InvalidPacket);
|
||||||
|
}
|
||||||
|
let alice_static_public_blob = &alice_static_info[..alice_static_public_blob_len];
|
||||||
|
alice_static_info = &alice_static_info[alice_static_public_blob_len..];
|
||||||
|
if alice_static_info.len() < 2 {
|
||||||
|
return Err(Error::InvalidPacket);
|
||||||
|
}
|
||||||
|
let meta_data_len = u16::from_le_bytes(alice_static_info[..2].try_into().unwrap()) as usize;
|
||||||
|
alice_static_info = &alice_static_info[2..];
|
||||||
|
if alice_static_info.len() < meta_data_len {
|
||||||
|
return Err(Error::InvalidPacket);
|
||||||
|
}
|
||||||
|
let meta_data = &alice_static_info[..meta_data_len];
|
||||||
|
|
||||||
|
if let Some((bob_session_id, psk, app_data)) =
|
||||||
|
app.accept_new_session(self, remote_address, alice_static_public_blob, meta_data)
|
||||||
|
{
|
||||||
|
let noise_es_ee_se_hk_psk = Secret(hmac_sha512(
|
||||||
|
&hmac_sha512(
|
||||||
|
noise_es_ee.as_bytes(),
|
||||||
|
bob_noise_e_secret
|
||||||
|
.agree(
|
||||||
|
&Application::extract_s_public_from_raw(alice_static_public_blob)
|
||||||
|
.ok_or(Error::FailedAuthentication)?,
|
||||||
|
)
|
||||||
|
.ok_or(Error::FailedAuthentication)?
|
||||||
|
.as_bytes(),
|
||||||
|
),
|
||||||
|
&hmac_sha512(psk.as_bytes(), hk.as_bytes()),
|
||||||
|
));
|
||||||
|
|
||||||
|
// Final authentication with the whole enchelada.
|
||||||
|
if !secure_eq(
|
||||||
|
&pkt_assembled[pkt_assembled_enc_end + HMAC_SHA384_SIZE..],
|
||||||
|
&hmac_sha384_2(
|
||||||
|
&kbkdf512(noise_es_ee_se_hk_psk.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION).as_bytes()
|
||||||
|
[..HMAC_SHA384_SIZE],
|
||||||
|
&message_nonce,
|
||||||
|
&pkt_assembled[HEADER_SIZE..pkt_assembled_enc_end + HMAC_SHA384_SIZE],
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
return Err(Error::FailedAuthentication);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(Error::NewSessionRejected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PACKET_TYPE_ALICE_REKEY_INIT => {}
|
PACKET_TYPE_ALICE_REKEY_INIT => {}
|
||||||
|
|
||||||
|
@ -1277,6 +1467,7 @@ impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
/// Create an send an ephemeral offer, populating ret_ephemeral_offer on success.
|
/// Create an send an ephemeral offer, populating ret_ephemeral_offer on success.
|
||||||
fn send_ephemeral_offer<SendFunction: FnMut(&mut [u8])>(
|
fn send_ephemeral_offer<SendFunction: FnMut(&mut [u8])>(
|
||||||
send: &mut SendFunction,
|
send: &mut SendFunction,
|
||||||
|
@ -1422,6 +1613,7 @@ fn send_ephemeral_offer<SendFunction: FnMut(&mut [u8])>(
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
fn set_packet_header(
|
fn set_packet_header(
|
||||||
packet: &mut [u8],
|
packet: &mut [u8],
|
||||||
|
@ -1486,12 +1678,13 @@ fn send_with_fragmentation<SendFunction: FnMut(&mut [u8])>(
|
||||||
packet: &mut [u8],
|
packet: &mut [u8],
|
||||||
mtu: usize,
|
mtu: usize,
|
||||||
packet_type: u8,
|
packet_type: u8,
|
||||||
recipient_session_id: u64,
|
recipient_session_id: Option<SessionId>,
|
||||||
ratchet_count: u64,
|
ratchet_count: u64,
|
||||||
counter: u64,
|
counter: u64,
|
||||||
header_check_cipher: &Aes,
|
header_check_cipher: Option<&Aes>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let packet_len = packet.len();
|
let packet_len = packet.len();
|
||||||
|
let recipient_session_id = recipient_session_id.map_or(SessionId::NONE, |s| u64::from(s));
|
||||||
let fragment_count = ((packet_len as f32) / (mtu as f32)).ceil() as usize;
|
let fragment_count = ((packet_len as f32) / (mtu as f32)).ceil() as usize;
|
||||||
let mut fragment_start = 0;
|
let mut fragment_start = 0;
|
||||||
let mut fragment_end = packet_len.min(mtu);
|
let mut fragment_end = packet_len.min(mtu);
|
||||||
|
@ -1506,7 +1699,9 @@ fn send_with_fragmentation<SendFunction: FnMut(&mut [u8])>(
|
||||||
ratchet_count,
|
ratchet_count,
|
||||||
counter,
|
counter,
|
||||||
)?;
|
)?;
|
||||||
header_check_cipher.encrypt_block_in_place(&mut fragment[6..22]);
|
if let Some(hcc) = header_check_cipher {
|
||||||
|
hcc.encrypt_block_in_place(&mut fragment[6..22]);
|
||||||
|
}
|
||||||
send(fragment);
|
send(fragment);
|
||||||
fragment_start = fragment_end - HEADER_SIZE;
|
fragment_start = fragment_end - HEADER_SIZE;
|
||||||
fragment_end = (fragment_start + mtu).min(packet_len);
|
fragment_end = (fragment_start + mtu).min(packet_len);
|
||||||
|
@ -1514,6 +1709,7 @@ fn send_with_fragmentation<SendFunction: FnMut(&mut [u8])>(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
/// Parse KEY_OFFER and KEY_COUNTER_OFFER starting after the unencrypted public key part.
|
/// Parse KEY_OFFER and KEY_COUNTER_OFFER starting after the unencrypted public key part.
|
||||||
fn parse_dec_key_offer_after_header(
|
fn parse_dec_key_offer_after_header(
|
||||||
incoming_packet: &[u8],
|
incoming_packet: &[u8],
|
||||||
|
@ -1666,6 +1862,7 @@ fn varint_safe_read(src: &mut &[u8]) -> Result<u64, Error> {
|
||||||
*src = b;
|
*src = b;
|
||||||
Ok(v)
|
Ok(v)
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
/// Shortcut to HMAC data split into two slices.
|
/// Shortcut to HMAC data split into two slices.
|
||||||
fn hmac_sha384_2(key: &[u8], a: &[u8], b: &[u8]) -> [u8; 48] {
|
fn hmac_sha384_2(key: &[u8], a: &[u8], b: &[u8]) -> [u8; 48] {
|
||||||
|
@ -1681,10 +1878,26 @@ 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 that can be used as a public key fingerprint to check ratcheting during key exchange.
|
/// Get an initialized AesGcm cipher for Bob to encrypt or decrypt his "note to self."
|
||||||
fn public_fingerprint_of_secret(key: &[u8]) -> [u8; 48] {
|
/// This both creates the cipher and initializes it with an IV.
|
||||||
let mut tmp = SHA384::new();
|
fn get_note_to_self_cipher(encrypt: bool, iv: &[u8]) -> AesGcm {
|
||||||
tmp.update(&[0xf0, 0x0d]); // arbitrary salt
|
const NOTE_TO_SELF_MASTER_SECRET: RwLock<Option<Secret<64>>> = RwLock::new(None);
|
||||||
tmp.update(key);
|
let msl_static = &NOTE_TO_SELF_MASTER_SECRET;
|
||||||
tmp.finish()
|
let msl = msl_static.read().unwrap();
|
||||||
|
let s = if let Some(ms) = msl.as_ref() {
|
||||||
|
Secret(hmac_sha512(ms.as_bytes(), iv))
|
||||||
|
} else {
|
||||||
|
drop(msl);
|
||||||
|
Secret(hmac_sha512(
|
||||||
|
msl_static
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.get_or_insert_with(|| Secret(random::get_bytes_secure()))
|
||||||
|
.as_bytes(),
|
||||||
|
iv,
|
||||||
|
))
|
||||||
|
};
|
||||||
|
let mut c = AesGcm::new(&s.as_bytes()[0..AES_KEY_SIZE], encrypt);
|
||||||
|
c.reset_init_gcm(&s.as_bytes()[AES_KEY_SIZE..AES_KEY_SIZE + AES_GCM_NONCE_SIZE]);
|
||||||
|
c
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue