mirror of
https://github.com/ZeroTier/ZeroTierOne
synced 2025-08-22 22:33:58 -07:00
Almost ready to test...
This commit is contained in:
parent
967dcaf377
commit
6bc69d1465
8 changed files with 531 additions and 318 deletions
|
@ -5,6 +5,22 @@ license = "MPL-2.0"
|
||||||
name = "zssp"
|
name = "zssp"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
opt-level = 3
|
||||||
|
lto = true
|
||||||
|
codegen-units = 1
|
||||||
|
panic = 'abort'
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "zssp"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
doc = true
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "zssp_test"
|
||||||
|
path = "src/main.rs"
|
||||||
|
doc = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
zerotier-utils = { path = "../utils" }
|
zerotier-utils = { path = "../utils" }
|
||||||
zerotier-crypto = { path = "../crypto" }
|
zerotier-crypto = { path = "../crypto" }
|
||||||
|
|
|
@ -6,32 +6,59 @@
|
||||||
* https://www.zerotier.com/
|
* https://www.zerotier.com/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use zerotier_crypto::p384::{P384KeyPair, P384PublicKey};
|
use zerotier_crypto::p384::P384KeyPair;
|
||||||
|
|
||||||
/// Trait to implement to integrate the session into an application.
|
/// Trait to implement to integrate the session into an application.
|
||||||
///
|
///
|
||||||
/// Templating the session on this trait lets the code here be almost entirely transport, OS,
|
/// Templating the session on this trait lets the code here be almost entirely transport, OS,
|
||||||
/// and use case independent.
|
/// and use case independent.
|
||||||
|
///
|
||||||
|
/// The constants exposed in this trait can be redefined from their defaults to change rekey
|
||||||
|
/// and negotiation timeout behavior. This is discouraged except for testing purposes when low
|
||||||
|
/// key lifetime values may be desirable to test rekeying. Also note that each side takes turns
|
||||||
|
/// initiating rekey, so if both sides don't have the same values you'll get asymmetric timing
|
||||||
|
/// behavior. This will still work as long as the key usage counter doesn't exceed the
|
||||||
|
/// EXPIRE_AFTER_USES limit.
|
||||||
pub trait ApplicationLayer: Sized {
|
pub trait ApplicationLayer: Sized {
|
||||||
/// Arbitrary opaque object associated with a session, such as a connection state object.
|
/// Rekey after this many key uses.
|
||||||
|
///
|
||||||
|
/// The default is 1/4 the recommended NIST limit for AES-GCM. Unless you are transferring
|
||||||
|
/// a massive amount of data REKEY_AFTER_TIME_MS is probably going to kick in first.
|
||||||
|
const REKEY_AFTER_USES: u64 = 536870912;
|
||||||
|
|
||||||
|
/// Hard expiration after this many uses.
|
||||||
|
///
|
||||||
|
/// Attempting to encrypt more than this many messages with a key will cause a hard error
|
||||||
|
/// and the internal erasure of ephemeral key material. You'll only ever hit this if something
|
||||||
|
/// goes wrong and rekeying fails.
|
||||||
|
const EXPIRE_AFTER_USES: u64 = 2147483648;
|
||||||
|
|
||||||
|
/// Start attempting to rekey after a key has been in use for this many milliseconds.
|
||||||
|
///
|
||||||
|
/// Default is two hours.
|
||||||
|
const REKEY_AFTER_TIME_MS: i64 = 1000 * 60 * 60 * 2;
|
||||||
|
|
||||||
|
/// Maximum random jitter to add to rekey-after time.
|
||||||
|
///
|
||||||
|
/// Default is ten minutes.
|
||||||
|
const REKEY_AFTER_TIME_MS_MAX_JITTER: u32 = 1000 * 60 * 10;
|
||||||
|
|
||||||
|
/// Timeout for Noise_XK session negotiation in milliseconds.
|
||||||
|
///
|
||||||
|
/// Default is two seconds, which should be enough for even extremely slow links or links
|
||||||
|
/// over very long distances.
|
||||||
|
const SESSION_NEGOTIATION_TIMEOUT_MS: i64 = 2000;
|
||||||
|
|
||||||
|
/// Type for arbitrary opaque object for use by the application that is attached to each session.
|
||||||
type Data;
|
type Data;
|
||||||
|
|
||||||
/// A buffer containing data read from the network that can be cached.
|
/// Data type for incoming packet buffers.
|
||||||
///
|
///
|
||||||
/// This can be e.g. a pooled buffer that automatically returns itself to the pool when dropped.
|
/// This can be something like Vec<u8> or Box<[u8]> or it can be something like a pooled reusable
|
||||||
/// It can also just be a Vec<u8> or Box<[u8]> or something like that.
|
/// buffer that automatically returns to its pool when ZSSP is done with it. ZSSP may hold these
|
||||||
|
/// for a short period of time when assembling fragmented packets on the receive path.
|
||||||
type IncomingPacketBuffer: AsRef<[u8]> + AsMut<[u8]>;
|
type IncomingPacketBuffer: AsRef<[u8]> + AsMut<[u8]>;
|
||||||
|
|
||||||
/// Rate limit for attempts to rekey existing sessions in milliseconds (default: 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_static_public_blob(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
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*
|
|
||||||
* (c) ZeroTier, Inc.
|
|
||||||
* https://www.zerotier.com/
|
|
||||||
*/
|
|
||||||
|
|
||||||
use crate::proto::{AES_GCM_TAG_SIZE, HEADER_SIZE};
|
|
||||||
|
|
||||||
/// Minimum size of a valid physical ZSSP packet of any type. Anything smaller is discarded.
|
|
||||||
pub const MIN_PACKET_SIZE: usize = HEADER_SIZE + AES_GCM_TAG_SIZE;
|
|
||||||
|
|
||||||
/// Minimum physical MTU for ZSSP to function.
|
|
||||||
pub const MIN_TRANSPORT_MTU: usize = 128;
|
|
||||||
|
|
||||||
/// Maximum size of init meta-data objects.
|
|
||||||
pub const MAX_METADATA_SIZE: usize = 256;
|
|
||||||
|
|
||||||
/// Start attempting to rekey after a key has been used to send packets this many times.
|
|
||||||
/// This is 1/4 the recommended NIST limit for AES-GCM key lifetimes under most conditions.
|
|
||||||
pub(crate) const REKEY_AFTER_USES: u64 = 536870912;
|
|
||||||
|
|
||||||
/// Hard expiration after this many uses.
|
|
||||||
///
|
|
||||||
/// Use of the key beyond this point is prohibited. If we reach this number of key uses
|
|
||||||
/// the key will be destroyed in memory and the session will cease to function. A hard
|
|
||||||
/// error is also generated.
|
|
||||||
pub(crate) const EXPIRE_AFTER_USES: u64 = REKEY_AFTER_USES * 2;
|
|
||||||
|
|
||||||
/// Start attempting to rekey after a key has been in use for this many milliseconds.
|
|
||||||
pub(crate) const REKEY_AFTER_TIME_MS: i64 = 1000 * 60 * 60; // 1 hour
|
|
||||||
|
|
||||||
/// Maximum random jitter to add to rekey-after time.
|
|
||||||
pub(crate) const REKEY_AFTER_TIME_MS_MAX_JITTER: u32 = 1000 * 60 * 10; // 10 minutes
|
|
||||||
|
|
||||||
/// Timeout for incoming sessions in incomplete state in milliseconds.
|
|
||||||
pub(crate) const INCOMPLETE_SESSION_TIMEOUT: i64 = 1000;
|
|
|
@ -6,11 +6,9 @@
|
||||||
* https://www.zerotier.com/
|
* https://www.zerotier.com/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use crate::sessionid::SessionId;
|
|
||||||
|
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// The packet was addressed to an unrecognized local session (should usually be ignored)
|
/// The packet was addressed to an unrecognized local session (should usually be ignored)
|
||||||
UnknownLocalSessionId(SessionId),
|
UnknownLocalSessionId,
|
||||||
|
|
||||||
/// Packet was not well formed
|
/// Packet was not well formed
|
||||||
InvalidPacket,
|
InvalidPacket,
|
||||||
|
@ -29,12 +27,6 @@ pub enum Error {
|
||||||
/// Attempt to send using session without established key
|
/// Attempt to send using session without established key
|
||||||
SessionNotEstablished,
|
SessionNotEstablished,
|
||||||
|
|
||||||
/// Packet ignored by rate limiter.
|
|
||||||
RateLimited,
|
|
||||||
|
|
||||||
/// Packet counter is too far outside window.
|
|
||||||
OutOfCounterWindow,
|
|
||||||
|
|
||||||
/// The other peer specified an unrecognized protocol version
|
/// The other peer specified an unrecognized protocol version
|
||||||
UnknownProtocolVersion,
|
UnknownProtocolVersion,
|
||||||
|
|
||||||
|
@ -44,6 +36,9 @@ pub enum Error {
|
||||||
/// Data object is too large to send, even with fragmentation
|
/// Data object is too large to send, even with fragmentation
|
||||||
DataTooLarge,
|
DataTooLarge,
|
||||||
|
|
||||||
|
/// Packet counter was outside window or packet arrived with session in an unexpected state.
|
||||||
|
OutOfSequence,
|
||||||
|
|
||||||
/// An unexpected buffer overrun occured while attempting to encode or decode a packet.
|
/// An unexpected buffer overrun occured while attempting to encode or decode a packet.
|
||||||
///
|
///
|
||||||
/// This can only ever happen if exceptionally large key blobs or metadata are being used,
|
/// This can only ever happen if exceptionally large key blobs or metadata are being used,
|
||||||
|
@ -61,20 +56,19 @@ impl From<std::io::Error> for Error {
|
||||||
|
|
||||||
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 {
|
f.write_str(match self {
|
||||||
Self::UnknownLocalSessionId(id) => f.write_str(format!("UnknownLocalSessionId({})", id).as_str()),
|
Self::UnknownLocalSessionId => "UnknownLocalSessionId",
|
||||||
Self::InvalidPacket => f.write_str("InvalidPacket"),
|
Self::InvalidPacket => "InvalidPacket",
|
||||||
Self::InvalidParameter => f.write_str("InvalidParameter"),
|
Self::InvalidParameter => "InvalidParameter",
|
||||||
Self::FailedAuthentication => f.write_str("FailedAuthentication"),
|
Self::FailedAuthentication => "FailedAuthentication",
|
||||||
Self::MaxKeyLifetimeExceeded => f.write_str("MaxKeyLifetimeExceeded"),
|
Self::MaxKeyLifetimeExceeded => "MaxKeyLifetimeExceeded",
|
||||||
Self::SessionNotEstablished => f.write_str("SessionNotEstablished"),
|
Self::SessionNotEstablished => "SessionNotEstablished",
|
||||||
Self::RateLimited => f.write_str("RateLimited"),
|
Self::UnknownProtocolVersion => "UnknownProtocolVersion",
|
||||||
Self::OutOfCounterWindow => f.write_str("OutOfCounterWindow"),
|
Self::DataBufferTooSmall => "DataBufferTooSmall",
|
||||||
Self::UnknownProtocolVersion => f.write_str("UnknownProtocolVersion"),
|
Self::DataTooLarge => "DataTooLarge",
|
||||||
Self::DataBufferTooSmall => f.write_str("DataBufferTooSmall"),
|
Self::OutOfSequence => "OutOfSequence",
|
||||||
Self::DataTooLarge => f.write_str("DataTooLarge"),
|
Self::UnexpectedBufferOverrun => "UnexpectedBufferOverrun",
|
||||||
Self::UnexpectedBufferOverrun => f.write_str("UnexpectedBufferOverrun"),
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,9 +13,8 @@ mod sessionid;
|
||||||
mod tests;
|
mod tests;
|
||||||
mod zssp;
|
mod zssp;
|
||||||
|
|
||||||
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::proto::{MAX_METADATA_SIZE, MIN_PACKET_SIZE, MIN_TRANSPORT_MTU};
|
||||||
pub use crate::sessionid::SessionId;
|
pub use crate::sessionid::SessionId;
|
||||||
pub use crate::zssp::{Context, ReceiveResult, Session};
|
pub use crate::zssp::{Context, ReceiveResult, Session};
|
||||||
|
|
3
zssp/src/main.rs
Normal file
3
zssp/src/main.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
use zssp::*;
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -13,10 +13,18 @@ use zerotier_crypto::hash::{HMAC_SHA384_SIZE, SHA384_HASH_SIZE};
|
||||||
use zerotier_crypto::p384::P384_PUBLIC_KEY_SIZE;
|
use zerotier_crypto::p384::P384_PUBLIC_KEY_SIZE;
|
||||||
|
|
||||||
use crate::applicationlayer::ApplicationLayer;
|
use crate::applicationlayer::ApplicationLayer;
|
||||||
use crate::constants::*;
|
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::sessionid::SessionId;
|
use crate::sessionid::SessionId;
|
||||||
|
|
||||||
|
/// Minimum size of a valid physical ZSSP packet of any type. Anything smaller is discarded.
|
||||||
|
pub const MIN_PACKET_SIZE: usize = HEADER_SIZE + AES_GCM_TAG_SIZE;
|
||||||
|
|
||||||
|
/// Minimum physical MTU for ZSSP to function.
|
||||||
|
pub const MIN_TRANSPORT_MTU: usize = 128;
|
||||||
|
|
||||||
|
/// Maximum size of init meta-data objects.
|
||||||
|
pub const MAX_METADATA_SIZE: usize = 256;
|
||||||
|
|
||||||
pub(crate) const SESSION_PROTOCOL_VERSION: u8 = 0x00;
|
pub(crate) const SESSION_PROTOCOL_VERSION: u8 = 0x00;
|
||||||
|
|
||||||
pub(crate) const COUNTER_WINDOW_MAX_OOO: usize = 16;
|
pub(crate) const COUNTER_WINDOW_MAX_OOO: usize = 16;
|
||||||
|
@ -30,8 +38,8 @@ 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 HEADER_SIZE: usize = 16;
|
pub(crate) const HEADER_SIZE: usize = 16;
|
||||||
pub(crate) const HEADER_CHECK_ENCRYPT_START: usize = 6;
|
pub(crate) const HEADER_PROTECT_ENCRYPT_START: usize = 6;
|
||||||
pub(crate) const HEADER_CHECK_ENCRYPT_END: usize = 22;
|
pub(crate) const HEADER_PROTECT_ENCRYPT_END: usize = 22;
|
||||||
|
|
||||||
pub(crate) const KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION: u8 = b'X'; // intermediate keys used in key exchanges
|
pub(crate) const KBKDF_KEY_USAGE_LABEL_KEX_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_KEX_AUTHENTICATION: u8 = b'x'; // intermediate keys used in key exchanges
|
||||||
|
@ -46,7 +54,7 @@ pub(crate) const MAX_NOISE_HANDSHAKE_SIZE: usize = MAX_NOISE_HANDSHAKE_FRAGMENTS
|
||||||
pub(crate) const BASE_KEY_SIZE: usize = 64;
|
pub(crate) const BASE_KEY_SIZE: usize = 64;
|
||||||
|
|
||||||
pub(crate) const AES_KEY_SIZE: usize = 32;
|
pub(crate) const AES_KEY_SIZE: usize = 32;
|
||||||
pub(crate) const AES_HEADER_CHECK_KEY_SIZE: usize = 16;
|
pub(crate) const AES_HEADER_PROTECTION_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;
|
pub(crate) const AES_CTR_NONCE_SIZE: usize = 12;
|
||||||
|
@ -62,14 +70,14 @@ pub(crate) struct AliceNoiseXKInit {
|
||||||
// -- start AES-CTR(es) encrypted section (IV is last 12 bytes of alice_noise_e))
|
// -- start AES-CTR(es) encrypted section (IV is last 12 bytes of alice_noise_e))
|
||||||
pub alice_session_id: [u8; SessionId::SIZE],
|
pub alice_session_id: [u8; SessionId::SIZE],
|
||||||
pub alice_hk_public: [u8; KYBER_PUBLICKEYBYTES],
|
pub alice_hk_public: [u8; KYBER_PUBLICKEYBYTES],
|
||||||
pub header_check_cipher_key: [u8; AES_HEADER_CHECK_KEY_SIZE],
|
pub header_protection_key: [u8; AES_HEADER_PROTECTION_KEY_SIZE],
|
||||||
// -- end encrypted section
|
// -- end encrypted section
|
||||||
pub hmac_es: [u8; HMAC_SHA384_SIZE],
|
pub hmac_es: [u8; HMAC_SHA384_SIZE],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AliceNoiseXKInit {
|
impl AliceNoiseXKInit {
|
||||||
pub const ENC_START: usize = HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE;
|
pub const ENC_START: usize = HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE;
|
||||||
pub const AUTH_START: usize = Self::ENC_START + SessionId::SIZE + KYBER_PUBLICKEYBYTES + AES_HEADER_CHECK_KEY_SIZE;
|
pub const AUTH_START: usize = Self::ENC_START + SessionId::SIZE + KYBER_PUBLICKEYBYTES + AES_HEADER_PROTECTION_KEY_SIZE;
|
||||||
pub const SIZE: usize = Self::AUTH_START + HMAC_SHA384_SIZE;
|
pub const SIZE: usize = Self::AUTH_START + HMAC_SHA384_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,7 +108,7 @@ impl BobNoiseXKAck {
|
||||||
pub(crate) struct AliceNoiseXKAck {
|
pub(crate) struct AliceNoiseXKAck {
|
||||||
pub header: [u8; HEADER_SIZE],
|
pub header: [u8; HEADER_SIZE],
|
||||||
pub session_protocol_version: u8,
|
pub session_protocol_version: u8,
|
||||||
// -- start AES-CTR(es_ee) encrypted section (IV is first 12 bytes of SHA384(hk))
|
// -- start AES-CTR(es_ee) encrypted section (IV is first 12 bytes of hk)
|
||||||
pub alice_static_blob_length: [u8; 2],
|
pub alice_static_blob_length: [u8; 2],
|
||||||
pub alice_static_blob: [u8; ???],
|
pub alice_static_blob: [u8; ???],
|
||||||
pub alice_metadata_length: [u8; 2],
|
pub alice_metadata_length: [u8; 2],
|
||||||
|
@ -111,27 +119,43 @@ pub(crate) struct AliceNoiseXKAck {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
pub(crate) const ALICE_NOISE_XK_ACK_ENC_START: usize = HEADER_SIZE + 1;
|
||||||
|
pub(crate) const ALICE_NOISE_XK_ACK_AUTH_SIZE: usize = HMAC_SHA384_SIZE + HMAC_SHA384_SIZE;
|
||||||
|
pub(crate) const ALICE_NOISE_XK_ACK_MIN_SIZE: usize = ALICE_NOISE_XK_ACK_ENC_START + 2 + 2 + ALICE_NOISE_XK_ACK_AUTH_SIZE;
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
#[repr(C, packed)]
|
#[repr(C, packed)]
|
||||||
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_noise_e: [u8; P384_PUBLIC_KEY_SIZE],
|
pub alice_e: [u8; P384_PUBLIC_KEY_SIZE],
|
||||||
// -- end AES-GCM encrypted portion
|
// -- end AES-GCM encrypted portion
|
||||||
pub gcm_mac: [u8; AES_GCM_TAG_SIZE],
|
pub gcm_mac: [u8; AES_GCM_TAG_SIZE],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AliceRekeyInit {
|
||||||
|
pub const ENC_START: usize = HEADER_SIZE;
|
||||||
|
pub const AUTH_START: usize = Self::ENC_START + P384_PUBLIC_KEY_SIZE;
|
||||||
|
pub const SIZE: usize = Self::AUTH_START + AES_GCM_TAG_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
#[repr(C, packed)]
|
#[repr(C, packed)]
|
||||||
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_noise_e: [u8; P384_PUBLIC_KEY_SIZE],
|
pub bob_e: [u8; P384_PUBLIC_KEY_SIZE],
|
||||||
pub ee_fingerprint: [u8; SHA384_HASH_SIZE],
|
pub next_key_fingerprint: [u8; SHA384_HASH_SIZE],
|
||||||
// -- end AES-GCM encrypted portion
|
// -- end AES-GCM encrypted portion
|
||||||
pub gcm_mac: [u8; AES_GCM_TAG_SIZE],
|
pub gcm_mac: [u8; AES_GCM_TAG_SIZE],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl BobRekeyAck {
|
||||||
|
pub const ENC_START: usize = HEADER_SIZE;
|
||||||
|
pub const AUTH_START: usize = Self::ENC_START + P384_PUBLIC_KEY_SIZE + SHA384_HASH_SIZE;
|
||||||
|
pub const SIZE: usize = Self::AUTH_START + AES_GCM_TAG_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
/// 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;
|
||||||
|
|
528
zssp/src/zssp.rs
528
zssp/src/zssp.rs
|
@ -16,7 +16,7 @@ use std::sync::{Arc, Mutex, RwLock, Weak};
|
||||||
|
|
||||||
use zerotier_crypto::aes::{Aes, AesCtr, AesGcm};
|
use zerotier_crypto::aes::{Aes, AesCtr, AesGcm};
|
||||||
use zerotier_crypto::hash::{hmac_sha512, HMACSHA384, HMAC_SHA384_SIZE, SHA384};
|
use zerotier_crypto::hash::{hmac_sha512, HMACSHA384, HMAC_SHA384_SIZE, SHA384};
|
||||||
use zerotier_crypto::p384::{P384KeyPair, P384PublicKey, P384_PUBLIC_KEY_SIZE};
|
use zerotier_crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE, P384_PUBLIC_KEY_SIZE};
|
||||||
use zerotier_crypto::secret::Secret;
|
use zerotier_crypto::secret::Secret;
|
||||||
use zerotier_crypto::{random, secure_eq};
|
use zerotier_crypto::{random, secure_eq};
|
||||||
|
|
||||||
|
@ -28,7 +28,6 @@ use zerotier_utils::ringbuffermap::RingBufferMap;
|
||||||
use pqc_kyber::{KYBER_SECRETKEYBYTES, KYBER_SSBYTES};
|
use pqc_kyber::{KYBER_SECRETKEYBYTES, KYBER_SSBYTES};
|
||||||
|
|
||||||
use crate::applicationlayer::ApplicationLayer;
|
use crate::applicationlayer::ApplicationLayer;
|
||||||
use crate::constants::*;
|
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::proto::*;
|
use crate::proto::*;
|
||||||
use crate::sessionid::SessionId;
|
use crate::sessionid::SessionId;
|
||||||
|
@ -52,11 +51,8 @@ pub enum ReceiveResult<'b, Application: ApplicationLayer> {
|
||||||
/// Packet was valid and a data payload was decoded and authenticated.
|
/// Packet was valid and a data payload was decoded and authenticated.
|
||||||
OkData(Arc<Session<Application>>, &'b mut [u8]),
|
OkData(Arc<Session<Application>>, &'b mut [u8]),
|
||||||
|
|
||||||
/// Packet was valid and a new session was created, with static public blob and optional meta-data.
|
/// Packet was valid and a new session was created.
|
||||||
OkNewSession(Arc<Session<Application>>, &'b [u8], Option<&'b [u8]>),
|
OkNewSession(Arc<Session<Application>>),
|
||||||
|
|
||||||
/// Packet appears valid but was ignored as a duplicate or as meaningless given the current state.
|
|
||||||
Ignored,
|
|
||||||
|
|
||||||
/// Packet appears valid but was rejected by the application layer, e.g. a rejected new session attempt.
|
/// Packet appears valid but was rejected by the application layer, e.g. a rejected new session attempt.
|
||||||
Rejected,
|
Rejected,
|
||||||
|
@ -75,8 +71,7 @@ pub struct Session<Application: ApplicationLayer> {
|
||||||
psk: Secret<BASE_KEY_SIZE>,
|
psk: Secret<BASE_KEY_SIZE>,
|
||||||
send_counter: AtomicU64,
|
send_counter: AtomicU64,
|
||||||
receive_window: [AtomicU64; COUNTER_WINDOW_MAX_OOO],
|
receive_window: [AtomicU64; COUNTER_WINDOW_MAX_OOO],
|
||||||
header_check_cipher: Aes,
|
header_protection_cipher: Aes,
|
||||||
offer: Mutex<EphemeralOffer>,
|
|
||||||
state: RwLock<State>,
|
state: RwLock<State>,
|
||||||
defrag: Mutex<RingBufferMap<u64, GatherArray<Application::IncomingPacketBuffer, MAX_FRAGMENTS>, 16, 16>>,
|
defrag: Mutex<RingBufferMap<u64, GatherArray<Application::IncomingPacketBuffer, MAX_FRAGMENTS>, 16, 16>>,
|
||||||
}
|
}
|
||||||
|
@ -89,41 +84,36 @@ struct SessionMaps<Application: ApplicationLayer> {
|
||||||
incomplete: HashMap<SessionId, Arc<NoiseXKIncoming>>,
|
incomplete: HashMap<SessionId, Arc<NoiseXKIncoming>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// State for an incoming incomplete Noise_XK session that isn't fully negotiated yet.
|
|
||||||
struct NoiseXKIncoming {
|
struct NoiseXKIncoming {
|
||||||
timestamp: i64,
|
timestamp: i64,
|
||||||
alice_session_id: SessionId,
|
alice_session_id: SessionId,
|
||||||
bob_session_id: SessionId,
|
bob_session_id: SessionId,
|
||||||
noise_es_ee: Secret<BASE_KEY_SIZE>,
|
noise_es_ee: Secret<BASE_KEY_SIZE>,
|
||||||
hk: Secret<KYBER_SSBYTES>,
|
hk: Secret<KYBER_SSBYTES>,
|
||||||
header_check_cipher_key: Secret<AES_HEADER_CHECK_KEY_SIZE>,
|
header_protection_key: Secret<AES_HEADER_PROTECTION_KEY_SIZE>,
|
||||||
bob_noise_e_secret: P384KeyPair,
|
bob_noise_e_secret: P384KeyPair,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// State that needs to be cached for the most recent outgoing offer.
|
struct NoiseXKOutgoing {
|
||||||
|
timestamp: i64,
|
||||||
|
alice_noise_e_secret: P384KeyPair,
|
||||||
|
noise_es: Secret<P384_ECDH_SHARED_SECRET_SIZE>,
|
||||||
|
alice_hk_secret: Secret<KYBER_SECRETKEYBYTES>,
|
||||||
|
}
|
||||||
|
|
||||||
enum EphemeralOffer {
|
enum EphemeralOffer {
|
||||||
None,
|
None,
|
||||||
NoiseXKInit(
|
NoiseXKInit(Box<NoiseXKOutgoing>),
|
||||||
// boxed to discard memory after use since we only enter this state once at setup
|
|
||||||
Box<(
|
|
||||||
// alice_e_secret, metadata, noise_es, alice_hk_public, alice_hk_secret, header check key
|
|
||||||
P384KeyPair,
|
|
||||||
Option<ArrayVec<u8, MAX_METADATA_SIZE>>,
|
|
||||||
Secret<48>,
|
|
||||||
Secret<KYBER_SECRETKEYBYTES>,
|
|
||||||
)>,
|
|
||||||
),
|
|
||||||
RekeyInit(P384KeyPair),
|
RekeyInit(P384KeyPair),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Other mutable state within the session.
|
|
||||||
struct State {
|
struct State {
|
||||||
remote_session_id: Option<SessionId>,
|
remote_session_id: Option<SessionId>,
|
||||||
keys: [Option<SessionKey>; 2],
|
keys: [Option<SessionKey>; 2],
|
||||||
current_key: usize,
|
current_key: usize,
|
||||||
|
offer: EphemeralOffer,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A session key with lifetime information.
|
|
||||||
struct SessionKey {
|
struct SessionKey {
|
||||||
ratchet_key: Secret<BASE_KEY_SIZE>, // Key used in derivation of the next session key
|
ratchet_key: Secret<BASE_KEY_SIZE>, // Key used in derivation of 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
|
||||||
|
@ -154,19 +144,28 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
/// Perform periodic background service and cleanup tasks.
|
/// Perform periodic background service and cleanup tasks.
|
||||||
///
|
///
|
||||||
/// This returns the number of milliseconds until it should be called again.
|
/// This returns the number of milliseconds until it should be called again.
|
||||||
pub fn service(&self, current_time: i64) -> i64 {
|
pub fn service<SendFunction: FnMut(&Arc<Session<Application>>, &mut [u8])>(&self, mut send: SendFunction, current_time: i64) -> i64 {
|
||||||
let mut dead_active = Vec::new();
|
let mut dead_active = Vec::new();
|
||||||
let mut dead_pending = Vec::new();
|
let mut dead_pending = Vec::new();
|
||||||
|
|
||||||
{
|
{
|
||||||
let sessions = self.sessions.read().unwrap();
|
let sessions = self.sessions.read().unwrap();
|
||||||
for (id, s) in sessions.active.iter() {
|
for (id, s) in sessions.active.iter() {
|
||||||
if s.strong_count() == 0 {
|
if let Some(session) = s.upgrade() {
|
||||||
|
let state = session.state.read().unwrap();
|
||||||
|
if let Some(key) = state.keys[state.current_key].as_ref() {
|
||||||
|
if key.role_is_bob
|
||||||
|
&& (current_time >= key.rekey_at_time || session.send_counter.load(Ordering::Relaxed) >= key.rekey_at_counter)
|
||||||
|
{
|
||||||
|
session.send_rekey(|b| send(&session, b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
dead_active.push(*id);
|
dead_active.push(*id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (id, p) in sessions.incomplete.iter() {
|
for (id, p) in sessions.incomplete.iter() {
|
||||||
if (p.timestamp - current_time) > INCOMPLETE_SESSION_TIMEOUT {
|
if (p.timestamp - current_time) > Application::SESSION_NEGOTIATION_TIMEOUT_MS {
|
||||||
dead_pending.push(*id);
|
dead_pending.push(*id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -182,7 +181,7 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
INCOMPLETE_SESSION_TIMEOUT * 2
|
Application::SESSION_NEGOTIATION_TIMEOUT_MS * 2
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new session and send initial packet(s) to other side.
|
/// Create a new session and send initial packet(s) to other side.
|
||||||
|
@ -191,9 +190,9 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
/// * `send` - User-supplied packet sending function
|
/// * `send` - User-supplied packet sending function
|
||||||
/// * `mtu` - Physical MTU for calls to send()
|
/// * `mtu` - Physical MTU for calls to send()
|
||||||
/// * `local_session_id` - This side's session ID
|
/// * `local_session_id` - This side's session ID
|
||||||
/// * `remote_s_public_blob` - Remote side's public key/identity blob
|
/// * `remote_s_public_p384` - Remote side's static public NIST P-384 key
|
||||||
/// * `metadata` - Optional metadata to be included in initial handshake
|
|
||||||
/// * `psk` - Pre-shared key (use all zero if none)
|
/// * `psk` - Pre-shared key (use all zero if none)
|
||||||
|
/// * `metadata` - Optional metadata to be included in initial handshake
|
||||||
/// * `application_data` - Arbitrary opaque data to include with session object
|
/// * `application_data` - Arbitrary opaque data to include with session object
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
pub fn open<SendFunction: FnMut(&mut [u8])>(
|
pub fn open<SendFunction: FnMut(&mut [u8])>(
|
||||||
|
@ -201,10 +200,11 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
app: &Application,
|
app: &Application,
|
||||||
mut send: SendFunction,
|
mut send: SendFunction,
|
||||||
mtu: usize,
|
mtu: usize,
|
||||||
remote_s_public_blob: &[u8],
|
remote_s_public_p384: &P384PublicKey,
|
||||||
metadata: Option<&[u8]>,
|
|
||||||
psk: Secret<BASE_KEY_SIZE>,
|
psk: Secret<BASE_KEY_SIZE>,
|
||||||
|
metadata: Option<&[u8]>,
|
||||||
application_data: Application::Data,
|
application_data: Application::Data,
|
||||||
|
current_time: i64,
|
||||||
) -> Result<Arc<Session<Application>>, Error> {
|
) -> Result<Arc<Session<Application>>, Error> {
|
||||||
if let Some(md) = metadata.as_ref() {
|
if let Some(md) = metadata.as_ref() {
|
||||||
if md.len() > MAX_METADATA_SIZE {
|
if md.len() > MAX_METADATA_SIZE {
|
||||||
|
@ -212,12 +212,11 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(bob_s_public) = Application::extract_s_public_from_static_public_blob(remote_s_public_blob) {
|
|
||||||
let alice_noise_e_secret = P384KeyPair::generate();
|
let alice_noise_e_secret = P384KeyPair::generate();
|
||||||
let alice_noise_e = alice_noise_e_secret.public_key_bytes().clone();
|
let alice_noise_e = alice_noise_e_secret.public_key_bytes().clone();
|
||||||
let noise_es = alice_noise_e_secret.agree(&bob_s_public).ok_or(Error::InvalidParameter)?;
|
let noise_es = alice_noise_e_secret.agree(&remote_s_public_p384).ok_or(Error::InvalidParameter)?;
|
||||||
let alice_hk_secret = pqc_kyber::keypair(&mut random::SecureRandom::default());
|
let alice_hk_secret = pqc_kyber::keypair(&mut random::SecureRandom::default());
|
||||||
let header_check_cipher_key: [u8; AES_HEADER_CHECK_KEY_SIZE] = random::get_bytes_secure();
|
let header_protection_key: [u8; AES_HEADER_PROTECTION_KEY_SIZE] = random::get_bytes_secure();
|
||||||
|
|
||||||
let (local_session_id, session) = {
|
let (local_session_id, session) = {
|
||||||
let mut sessions = self.sessions.write().unwrap();
|
let mut sessions = self.sessions.write().unwrap();
|
||||||
|
@ -236,14 +235,18 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
psk,
|
psk,
|
||||||
send_counter: AtomicU64::new(2), // 1 is the counter value for this INIT message
|
send_counter: AtomicU64::new(2), // 1 is the counter value for this INIT message
|
||||||
receive_window: std::array::from_fn(|_| AtomicU64::new(0)),
|
receive_window: std::array::from_fn(|_| AtomicU64::new(0)),
|
||||||
header_check_cipher: Aes::new(&header_check_cipher_key),
|
header_protection_cipher: Aes::new(&header_protection_key),
|
||||||
offer: Mutex::new(EphemeralOffer::NoiseXKInit(Box::new((
|
state: RwLock::new(State {
|
||||||
|
remote_session_id: None,
|
||||||
|
keys: [None, None],
|
||||||
|
current_key: 0,
|
||||||
|
offer: EphemeralOffer::NoiseXKInit(Box::new(NoiseXKOutgoing {
|
||||||
|
timestamp: current_time,
|
||||||
alice_noise_e_secret,
|
alice_noise_e_secret,
|
||||||
metadata.map(|md| ArrayVec::try_from(md).unwrap()),
|
noise_es: noise_es.clone(),
|
||||||
noise_es.clone(),
|
alice_hk_secret: Secret(alice_hk_secret.secret),
|
||||||
Secret(alice_hk_secret.secret),
|
})),
|
||||||
)))),
|
}),
|
||||||
state: RwLock::new(State { remote_session_id: None, keys: [None, None], current_key: 0 }),
|
|
||||||
defrag: Mutex::new(RingBufferMap::new(random::xorshift64_random() as u32)),
|
defrag: Mutex::new(RingBufferMap::new(random::xorshift64_random() as u32)),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -258,7 +261,7 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
init.alice_noise_e = alice_noise_e;
|
init.alice_noise_e = alice_noise_e;
|
||||||
init.alice_session_id = *local_session_id.as_bytes();
|
init.alice_session_id = *local_session_id.as_bytes();
|
||||||
init.alice_hk_public = alice_hk_secret.public;
|
init.alice_hk_public = alice_hk_secret.public;
|
||||||
init.header_check_cipher_key = header_check_cipher_key;
|
init.header_protection_key = header_protection_key;
|
||||||
|
|
||||||
let mut ctr = AesCtr::new(kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION>(noise_es.as_bytes()).as_bytes());
|
let mut ctr = AesCtr::new(kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION>(noise_es.as_bytes()).as_bytes());
|
||||||
ctr.reset_set_iv(&alice_noise_e[P384_PUBLIC_KEY_SIZE - AES_CTR_NONCE_SIZE..]);
|
ctr.reset_set_iv(&alice_noise_e[P384_PUBLIC_KEY_SIZE - AES_CTR_NONCE_SIZE..]);
|
||||||
|
@ -274,9 +277,6 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
send_with_fragmentation(&mut send, &mut init_buffer, mtu, PACKET_TYPE_ALICE_NOISE_XK_INIT, None, 0, 1, None)?;
|
send_with_fragmentation(&mut send, &mut init_buffer, mtu, PACKET_TYPE_ALICE_NOISE_XK_INIT, None, 0, 1, None)?;
|
||||||
|
|
||||||
return Ok(session);
|
return Ok(session);
|
||||||
} else {
|
|
||||||
return Err(Error::InvalidParameter);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Receive, authenticate, decrypt, and process a physical wire packet.
|
/// Receive, authenticate, decrypt, and process a physical wire packet.
|
||||||
|
@ -285,18 +285,38 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
/// wtth an active session this session is supplied, otherwise this parameter is None. The size
|
/// wtth an active session this session is supplied, otherwise this parameter is None. The size
|
||||||
/// of packets to be sent will not exceed the supplied mtu.
|
/// of packets to be sent will not exceed the supplied mtu.
|
||||||
///
|
///
|
||||||
|
/// The check_allow_incoming_session function is called when an initial Noise_XK init message is
|
||||||
|
/// received. This is before anything is known about the caller. A return value of true proceeds
|
||||||
|
/// with negotiation. False drops the packet.
|
||||||
|
///
|
||||||
|
/// The check_accept_session function is called at the end of negotiation for an incoming session
|
||||||
|
/// with the caller's static public blob and meta-data if any. It must return the P-384 static public
|
||||||
|
/// key extracted from the supplied blob, a PSK (or all zeroes if none), and application data to
|
||||||
|
/// associate with the new session. A return of None abandons the session.
|
||||||
|
///
|
||||||
|
/// Note that if check_accept_session accepts and returns Some() the session could still fail with
|
||||||
|
/// receive() returning an error. A Some() return from check_accept_sesion doesn't guarantee
|
||||||
|
/// successful new session init.
|
||||||
|
///
|
||||||
/// * `app` - Interface to application using ZSSP
|
/// * `app` - Interface to application using ZSSP
|
||||||
/// * `check_allow_incoming_session` - Function to call to check whether an unidentified new session should be accepted
|
/// * `check_allow_incoming_session` - Function to call to check whether an unidentified new session should be accepted
|
||||||
|
/// * `check_accept_session` - Function to accept sessions after final negotiation, or returns None if rejected
|
||||||
/// * `send` - Function to call to send packets
|
/// * `send` - Function to call to send packets
|
||||||
/// * `data_buf` - Buffer to receive decrypted and authenticated object data (an error is returned if too small)
|
/// * `data_buf` - Buffer to receive decrypted and authenticated object data (an error is returned if too small)
|
||||||
/// * `incoming_packet_buf` - Buffer containing incoming wire packet (receive() takes ownership)
|
/// * `incoming_packet_buf` - Buffer containing incoming wire packet (receive() takes ownership)
|
||||||
/// * `mtu` - Physical wire MTU for sending packets
|
/// * `mtu` - Physical wire MTU for sending packets
|
||||||
/// * `current_time` - Current monotonic time in milliseconds
|
/// * `current_time` - Current monotonic time in milliseconds
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn receive<'b, SendFunction: FnMut(Option<&Arc<Session<Application>>>, &mut [u8]), CheckAllowIncomingSession: FnMut() -> bool>(
|
pub fn receive<
|
||||||
|
'b,
|
||||||
|
SendFunction: FnMut(Option<&Arc<Session<Application>>>, &mut [u8]),
|
||||||
|
CheckAllowIncomingSession: FnMut() -> bool,
|
||||||
|
CheckAcceptSession: FnMut(&[u8], Option<&[u8]>) -> Option<(P384PublicKey, Secret<64>, Application::Data)>,
|
||||||
|
>(
|
||||||
&self,
|
&self,
|
||||||
app: &Application,
|
app: &Application,
|
||||||
mut check_allow_incoming_session: CheckAllowIncomingSession,
|
mut check_allow_incoming_session: CheckAllowIncomingSession,
|
||||||
|
mut check_accept_session: CheckAcceptSession,
|
||||||
mut send: SendFunction,
|
mut send: SendFunction,
|
||||||
data_buf: &'b mut [u8],
|
data_buf: &'b mut [u8],
|
||||||
mut incoming_packet_buf: Application::IncomingPacketBuffer,
|
mut incoming_packet_buf: Application::IncomingPacketBuffer,
|
||||||
|
@ -308,12 +328,12 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
return Err(Error::InvalidPacket);
|
return Err(Error::InvalidPacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut pending = None;
|
let mut incomplete = None;
|
||||||
if let Some(local_session_id) = SessionId::new_from_u64_le(memory::load_raw(incoming_packet)) {
|
if let Some(local_session_id) = SessionId::new_from_u64_le(memory::load_raw(incoming_packet)) {
|
||||||
if let Some(session) = self.look_up_session(local_session_id) {
|
if let Some(session) = self.look_up_session(local_session_id) {
|
||||||
session
|
session
|
||||||
.header_check_cipher
|
.header_protection_cipher
|
||||||
.decrypt_block_in_place(&mut incoming_packet[HEADER_CHECK_ENCRYPT_START..HEADER_CHECK_ENCRYPT_END]);
|
.decrypt_block_in_place(&mut incoming_packet[HEADER_PROTECT_ENCRYPT_START..HEADER_PROTECT_ENCRYPT_END]);
|
||||||
let (key_index, packet_type, fragment_count, fragment_no, counter) = parse_packet_header(&incoming_packet);
|
let (key_index, packet_type, fragment_count, fragment_no, counter) = parse_packet_header(&incoming_packet);
|
||||||
|
|
||||||
if session.check_receive_window(counter) {
|
if session.check_receive_window(counter) {
|
||||||
|
@ -327,6 +347,7 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
app,
|
app,
|
||||||
&mut send,
|
&mut send,
|
||||||
&mut check_allow_incoming_session,
|
&mut check_allow_incoming_session,
|
||||||
|
&mut check_accept_session,
|
||||||
data_buf,
|
data_buf,
|
||||||
counter,
|
counter,
|
||||||
assembled_packet.as_ref(),
|
assembled_packet.as_ref(),
|
||||||
|
@ -348,6 +369,7 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
app,
|
app,
|
||||||
&mut send,
|
&mut send,
|
||||||
&mut check_allow_incoming_session,
|
&mut check_allow_incoming_session,
|
||||||
|
&mut check_accept_session,
|
||||||
data_buf,
|
data_buf,
|
||||||
counter,
|
counter,
|
||||||
&[incoming_packet_buf],
|
&[incoming_packet_buf],
|
||||||
|
@ -360,15 +382,15 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::OutOfCounterWindow);
|
return Err(Error::OutOfSequence);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let Some(p) = self.sessions.read().unwrap().incomplete.get(&local_session_id).cloned() {
|
if let Some(p) = self.sessions.read().unwrap().incomplete.get(&local_session_id).cloned() {
|
||||||
Aes::new(p.header_check_cipher_key.as_bytes())
|
Aes::new(p.header_protection_key.as_bytes())
|
||||||
.decrypt_block_in_place(&mut incoming_packet[HEADER_CHECK_ENCRYPT_START..HEADER_CHECK_ENCRYPT_END]);
|
.decrypt_block_in_place(&mut incoming_packet[HEADER_PROTECT_ENCRYPT_START..HEADER_PROTECT_ENCRYPT_END]);
|
||||||
pending = Some(p);
|
incomplete = Some(p);
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::UnknownLocalSessionId(local_session_id));
|
return Err(Error::UnknownLocalSessionId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -386,12 +408,13 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
app,
|
app,
|
||||||
&mut send,
|
&mut send,
|
||||||
&mut check_allow_incoming_session,
|
&mut check_allow_incoming_session,
|
||||||
|
&mut check_accept_session,
|
||||||
data_buf,
|
data_buf,
|
||||||
counter,
|
counter,
|
||||||
assembled_packet.as_ref(),
|
assembled_packet.as_ref(),
|
||||||
packet_type,
|
packet_type,
|
||||||
None,
|
None,
|
||||||
pending,
|
incomplete,
|
||||||
key_index,
|
key_index,
|
||||||
mtu,
|
mtu,
|
||||||
current_time,
|
current_time,
|
||||||
|
@ -402,12 +425,13 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
app,
|
app,
|
||||||
&mut send,
|
&mut send,
|
||||||
&mut check_allow_incoming_session,
|
&mut check_allow_incoming_session,
|
||||||
|
&mut check_accept_session,
|
||||||
data_buf,
|
data_buf,
|
||||||
counter,
|
counter,
|
||||||
&[incoming_packet_buf],
|
&[incoming_packet_buf],
|
||||||
packet_type,
|
packet_type,
|
||||||
None,
|
None,
|
||||||
pending,
|
incomplete,
|
||||||
key_index,
|
key_index,
|
||||||
mtu,
|
mtu,
|
||||||
current_time,
|
current_time,
|
||||||
|
@ -421,30 +445,32 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
'b,
|
'b,
|
||||||
SendFunction: FnMut(Option<&Arc<Session<Application>>>, &mut [u8]),
|
SendFunction: FnMut(Option<&Arc<Session<Application>>>, &mut [u8]),
|
||||||
CheckAllowIncomingSession: FnMut() -> bool,
|
CheckAllowIncomingSession: FnMut() -> bool,
|
||||||
|
CheckAcceptSession: FnMut(&[u8], Option<&[u8]>) -> Option<(P384PublicKey, Secret<64>, Application::Data)>,
|
||||||
>(
|
>(
|
||||||
&self,
|
&self,
|
||||||
app: &Application,
|
app: &Application,
|
||||||
send: &mut SendFunction,
|
send: &mut SendFunction,
|
||||||
check_allow_incoming_session: &mut CheckAllowIncomingSession,
|
check_allow_incoming_session: &mut CheckAllowIncomingSession,
|
||||||
|
check_accept_session: &mut CheckAcceptSession,
|
||||||
data_buf: &'b mut [u8],
|
data_buf: &'b mut [u8],
|
||||||
counter: u64,
|
counter: u64,
|
||||||
fragments: &[Application::IncomingPacketBuffer],
|
fragments: &[Application::IncomingPacketBuffer],
|
||||||
packet_type: u8,
|
packet_type: u8,
|
||||||
session: Option<Arc<Session<Application>>>,
|
session: Option<Arc<Session<Application>>>,
|
||||||
pending: Option<Arc<NoiseXKIncoming>>,
|
incomplete: Option<Arc<NoiseXKIncoming>>,
|
||||||
key_index: usize,
|
key_index: usize,
|
||||||
mtu: usize,
|
mtu: usize,
|
||||||
current_time: i64,
|
current_time: i64,
|
||||||
) -> Result<ReceiveResult<'b, Application>, Error> {
|
) -> Result<ReceiveResult<'b, Application>, Error> {
|
||||||
debug_assert!(fragments.len() >= 1);
|
debug_assert!(fragments.len() >= 1);
|
||||||
|
|
||||||
let message_nonce = create_message_nonce(packet_type, counter);
|
let incoming_message_nonce = create_message_nonce(packet_type, counter);
|
||||||
if packet_type == PACKET_TYPE_DATA {
|
if packet_type == PACKET_TYPE_DATA {
|
||||||
if let Some(session) = session {
|
if let Some(session) = session {
|
||||||
let state = session.state.read().unwrap();
|
let state = session.state.read().unwrap();
|
||||||
if let Some(session_key) = state.keys[key_index].as_ref() {
|
if let Some(key) = state.keys[key_index].as_ref() {
|
||||||
let mut c = session_key.get_receive_cipher();
|
let mut c = key.get_receive_cipher();
|
||||||
c.reset_init_gcm(&message_nonce);
|
c.reset_init_gcm(&incoming_message_nonce);
|
||||||
|
|
||||||
let mut data_len = 0;
|
let mut data_len = 0;
|
||||||
|
|
||||||
|
@ -455,7 +481,7 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
let current_frag_data_start = data_len;
|
let current_frag_data_start = data_len;
|
||||||
data_len += f.len() - HEADER_SIZE;
|
data_len += f.len() - HEADER_SIZE;
|
||||||
if data_len > data_buf.len() {
|
if data_len > data_buf.len() {
|
||||||
session_key.return_receive_cipher(c);
|
key.return_receive_cipher(c);
|
||||||
return Err(Error::DataBufferTooSmall);
|
return Err(Error::DataBufferTooSmall);
|
||||||
}
|
}
|
||||||
c.crypt(&f[HEADER_SIZE..], &mut data_buf[current_frag_data_start..data_len]);
|
c.crypt(&f[HEADER_SIZE..], &mut data_buf[current_frag_data_start..data_len]);
|
||||||
|
@ -469,7 +495,7 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
}
|
}
|
||||||
data_len += last_fragment.len() - (HEADER_SIZE + AES_GCM_TAG_SIZE);
|
data_len += last_fragment.len() - (HEADER_SIZE + AES_GCM_TAG_SIZE);
|
||||||
if data_len > data_buf.len() {
|
if data_len > data_buf.len() {
|
||||||
session_key.return_receive_cipher(c);
|
key.return_receive_cipher(c);
|
||||||
return Err(Error::DataBufferTooSmall);
|
return Err(Error::DataBufferTooSmall);
|
||||||
}
|
}
|
||||||
let payload_end = last_fragment.len() - AES_GCM_TAG_SIZE;
|
let payload_end = last_fragment.len() - AES_GCM_TAG_SIZE;
|
||||||
|
@ -479,17 +505,17 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
);
|
);
|
||||||
|
|
||||||
let aead_authentication_ok = c.finish_decrypt(&last_fragment[payload_end..]);
|
let aead_authentication_ok = c.finish_decrypt(&last_fragment[payload_end..]);
|
||||||
session_key.return_receive_cipher(c);
|
key.return_receive_cipher(c);
|
||||||
|
|
||||||
if aead_authentication_ok {
|
if aead_authentication_ok {
|
||||||
if session.update_receive_window(counter) {
|
if session.update_receive_window(counter) {
|
||||||
// If the packet authenticated, this confirms that the other side indeed
|
// If the packet authenticated, this confirms that the other side indeed
|
||||||
// knows this session key. In that case mark the session key as confirmed
|
// knows this session key. In that case mark the session key as confirmed
|
||||||
// and if the current active key is older switch it to point to this one.
|
// and if the current active key is older switch it to point to this one.
|
||||||
if session_key.confirmed {
|
if key.confirmed {
|
||||||
drop(state);
|
drop(state);
|
||||||
} else {
|
} else {
|
||||||
let key_created_at_counter = session_key.created_at_counter;
|
let key_created_at_counter = key.created_at_counter;
|
||||||
drop(state);
|
drop(state);
|
||||||
|
|
||||||
let mut state = session.state.write().unwrap();
|
let mut state = session.state.write().unwrap();
|
||||||
|
@ -507,14 +533,14 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
|
|
||||||
return Ok(ReceiveResult::OkData(session, &mut data_buf[..data_len]));
|
return Ok(ReceiveResult::OkData(session, &mut data_buf[..data_len]));
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::OutOfCounterWindow);
|
return Err(Error::OutOfSequence);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Err(Error::FailedAuthentication);
|
return Err(Error::FailedAuthentication);
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::SessionNotEstablished);
|
return Err(Error::UnknownLocalSessionId);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// For Noise setup/KEX packets go ahead and pre-assemble all fragments to simplify the code below.
|
// For Noise setup/KEX packets go ahead and pre-assemble all fragments to simplify the code below.
|
||||||
|
@ -547,8 +573,8 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
* to the current exchange.
|
* to the current exchange.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if session.is_some() || counter != 1 {
|
if session.is_some() || incomplete.is_some() || counter != 1 {
|
||||||
return Err(Error::OutOfCounterWindow);
|
return Err(Error::OutOfSequence);
|
||||||
}
|
}
|
||||||
|
|
||||||
let pkt: &AliceNoiseXKInit = byte_array_as_proto_buffer(pkt_assembled)?;
|
let pkt: &AliceNoiseXKInit = byte_array_as_proto_buffer(pkt_assembled)?;
|
||||||
|
@ -560,7 +586,7 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
&pkt.hmac_es,
|
&pkt.hmac_es,
|
||||||
&hmac_sha384_2(
|
&hmac_sha384_2(
|
||||||
kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION>(noise_es.as_bytes()).as_bytes(),
|
kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION>(noise_es.as_bytes()).as_bytes(),
|
||||||
&message_nonce,
|
&incoming_message_nonce,
|
||||||
&pkt_assembled[HEADER_SIZE..AliceNoiseXKInit::AUTH_START],
|
&pkt_assembled[HEADER_SIZE..AliceNoiseXKInit::AUTH_START],
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
|
@ -610,10 +636,10 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
if sessions.incomplete.len() >= self.max_incomplete_session_queue_size {
|
if sessions.incomplete.len() >= self.max_incomplete_session_queue_size {
|
||||||
// If this queue is too big, we remove the latest entry and replace it. The latest
|
// If this queue is too big, we remove the latest entry and replace it. The latest
|
||||||
// is used because under flood conditions this is most likely to be another bogus
|
// is used because under flood conditions this is most likely to be another bogus
|
||||||
// entry. If we find one that is actually timed out, that always gets replaced.
|
// entry. If we find one that is actually timed out, that one is replaced instead.
|
||||||
let mut newest = i64::MIN;
|
let mut newest = i64::MIN;
|
||||||
let mut replace_id = None;
|
let mut replace_id = None;
|
||||||
let cutoff_time = current_time - INCOMPLETE_SESSION_TIMEOUT;
|
let cutoff_time = current_time - Application::SESSION_NEGOTIATION_TIMEOUT_MS;
|
||||||
for (id, s) in sessions.incomplete.iter() {
|
for (id, s) in sessions.incomplete.iter() {
|
||||||
if s.timestamp <= cutoff_time {
|
if s.timestamp <= cutoff_time {
|
||||||
replace_id = Some(*id);
|
replace_id = Some(*id);
|
||||||
|
@ -635,7 +661,7 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
noise_es_ee: noise_es_ee.clone(),
|
noise_es_ee: noise_es_ee.clone(),
|
||||||
hk,
|
hk,
|
||||||
bob_noise_e_secret,
|
bob_noise_e_secret,
|
||||||
header_check_cipher_key: Secret(pkt.header_check_cipher_key),
|
header_protection_key: Secret(pkt.header_protection_key),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -672,7 +698,7 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
Some(alice_session_id),
|
Some(alice_session_id),
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
Some(&Aes::new(&pkt.header_check_cipher_key)),
|
Some(&Aes::new(&pkt.header_protection_key)),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
return Ok(ReceiveResult::Ok);
|
return Ok(ReceiveResult::Ok);
|
||||||
|
@ -687,13 +713,13 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
* the negotiation.
|
* the negotiation.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if counter != 1 {
|
if counter != 1 || incomplete.is_some() {
|
||||||
return Err(Error::OutOfCounterWindow);
|
return Err(Error::OutOfSequence);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(session) = session {
|
if let Some(session) = session {
|
||||||
let mut offer = session.offer.lock().unwrap();
|
let state = session.state.read().unwrap();
|
||||||
if let EphemeralOffer::NoiseXKInit(boxed_offer) = &*offer {
|
if let EphemeralOffer::NoiseXKInit(boxed_offer) = &state.offer {
|
||||||
let (alice_e_secret, metadata, noise_es, alice_hk_secret) = boxed_offer.as_ref();
|
let (alice_e_secret, metadata, noise_es, alice_hk_secret) = boxed_offer.as_ref();
|
||||||
let pkt: &BobNoiseXKAck = byte_array_as_proto_buffer(pkt_assembled)?;
|
let pkt: &BobNoiseXKAck = byte_array_as_proto_buffer(pkt_assembled)?;
|
||||||
|
|
||||||
|
@ -714,7 +740,7 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
&pkt.hmac_es_ee,
|
&pkt.hmac_es_ee,
|
||||||
&hmac_sha384_2(
|
&hmac_sha384_2(
|
||||||
noise_es_ee_kex_hmac_key.as_bytes(),
|
noise_es_ee_kex_hmac_key.as_bytes(),
|
||||||
&message_nonce,
|
&incoming_message_nonce,
|
||||||
&pkt_assembled[HEADER_SIZE..BobNoiseXKAck::AUTH_START],
|
&pkt_assembled[HEADER_SIZE..BobNoiseXKAck::AUTH_START],
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
|
@ -749,6 +775,7 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION>(noise_es_ee_se_hk_psk.as_bytes());
|
kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION>(noise_es_ee_se_hk_psk.as_bytes());
|
||||||
|
|
||||||
let reply_counter = session.get_next_outgoing_counter().ok_or(Error::MaxKeyLifetimeExceeded)?;
|
let reply_counter = session.get_next_outgoing_counter().ok_or(Error::MaxKeyLifetimeExceeded)?;
|
||||||
|
debug_assert_eq!(reply_counter.get(), 2);
|
||||||
let reply_message_nonce = create_message_nonce(PACKET_TYPE_ALICE_NOISE_XK_ACK, reply_counter.get());
|
let reply_message_nonce = create_message_nonce(PACKET_TYPE_ALICE_NOISE_XK_ACK, reply_counter.get());
|
||||||
|
|
||||||
// Create reply informing Bob of our static identity now that we've verified Bob and set
|
// Create reply informing Bob of our static identity now that we've verified Bob and set
|
||||||
|
@ -777,7 +804,7 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
// is a hash of 'hk' making it actually a secret and "borrowing" a little PQ
|
// is a hash of 'hk' making it actually a secret and "borrowing" a little PQ
|
||||||
// forward secrecy for Alice's identity.
|
// forward secrecy for Alice's identity.
|
||||||
let mut ctr = AesCtr::new(noise_es_ee_kex_enc_key.as_bytes());
|
let mut ctr = AesCtr::new(noise_es_ee_kex_enc_key.as_bytes());
|
||||||
ctr.reset_set_iv(&SHA384::hash(hk.as_bytes())[..AES_CTR_NONCE_SIZE]);
|
ctr.reset_set_iv(&hk.as_bytes()[..AES_CTR_NONCE_SIZE]);
|
||||||
ctr.crypt_in_place(&mut reply_buffer[HEADER_SIZE + 1..reply_len]);
|
ctr.crypt_in_place(&mut reply_buffer[HEADER_SIZE + 1..reply_len]);
|
||||||
|
|
||||||
// First attach HMAC allowing Bob to verify that this is from the same Alice and to
|
// First attach HMAC allowing Bob to verify that this is from the same Alice and to
|
||||||
|
@ -801,15 +828,11 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
reply_buffer[reply_len..reply_len + HMAC_SHA384_SIZE].copy_from_slice(&hmac_es_ee_se_hk_psk);
|
reply_buffer[reply_len..reply_len + HMAC_SHA384_SIZE].copy_from_slice(&hmac_es_ee_se_hk_psk);
|
||||||
reply_len += HMAC_SHA384_SIZE;
|
reply_len += HMAC_SHA384_SIZE;
|
||||||
|
|
||||||
// Clear the offer field since we're finished handling a response to our initial offer.
|
drop(state);
|
||||||
*offer = EphemeralOffer::None;
|
|
||||||
drop(offer);
|
|
||||||
|
|
||||||
// Learn Bob's session ID and the first session key.
|
|
||||||
{
|
{
|
||||||
let mut state = session.state.write().unwrap();
|
let mut state = session.state.write().unwrap();
|
||||||
let _ = state.remote_session_id.insert(bob_session_id);
|
let _ = state.remote_session_id.insert(bob_session_id);
|
||||||
let _ = state.keys[0].insert(SessionKey::new(
|
let _ = state.keys[0].insert(SessionKey::new::<Application>(
|
||||||
noise_es_ee_se_hk_psk,
|
noise_es_ee_se_hk_psk,
|
||||||
current_time,
|
current_time,
|
||||||
reply_counter.get(),
|
reply_counter.get(),
|
||||||
|
@ -817,6 +840,7 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
false,
|
false,
|
||||||
));
|
));
|
||||||
state.current_key = 0;
|
state.current_key = 0;
|
||||||
|
state.offer = EphemeralOffer::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
send_with_fragmentation(
|
send_with_fragmentation(
|
||||||
|
@ -827,7 +851,7 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
Some(bob_session_id),
|
Some(bob_session_id),
|
||||||
0,
|
0,
|
||||||
reply_counter.get(),
|
reply_counter.get(),
|
||||||
Some(&session.header_check_cipher),
|
Some(&session.header_protection_cipher),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
return Ok(ReceiveResult::Ok);
|
return Ok(ReceiveResult::Ok);
|
||||||
|
@ -835,10 +859,10 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
return Err(Error::InvalidPacket);
|
return Err(Error::InvalidPacket);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Ok(ReceiveResult::Ignored);
|
return Err(Error::OutOfSequence);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::SessionNotEstablished);
|
return Err(Error::UnknownLocalSessionId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -853,120 +877,245 @@ impl<Application: ApplicationLayer> Context<Application> {
|
||||||
* that Alice must return.
|
* that Alice must return.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if session.is_some() {
|
if session.is_some() || counter != 2 {
|
||||||
return Ok(ReceiveResult::Ignored);
|
return Err(Error::OutOfSequence);
|
||||||
|
}
|
||||||
|
if pkt_assembled.len() < ALICE_NOISE_XK_ACK_MIN_SIZE {
|
||||||
|
return Err(Error::InvalidPacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
if let Some(incomplete) = incomplete {
|
||||||
// Restore state from note to self, returning to where we were after Alice's first contact.
|
// Check timeout, negotiations aren't allowed to take longer than this.
|
||||||
let alice_session_id = SessionId::new_from_bytes(&bob_note_to_self.alice_session_id).ok_or(Error::InvalidPacket)?;
|
if (current_time - incomplete.timestamp) > Application::SESSION_NEGOTIATION_TIMEOUT_MS {
|
||||||
let bob_noise_e_secret = P384KeyPair::from_bytes(&bob_note_to_self.bob_noise_e, &bob_note_to_self.bob_noise_e_secret)
|
return Err(Error::UnknownLocalSessionId);
|
||||||
.ok_or(Error::InvalidPacket)?;
|
}
|
||||||
let hk = Secret(bob_note_to_self.hk);
|
|
||||||
let noise_es_ee = Secret(bob_note_to_self.noise_es_ee);
|
|
||||||
let header_check_cipher = Aes::new(&bob_note_to_self.header_check_cipher_key);
|
|
||||||
drop(bob_note_to_self_buffer);
|
|
||||||
|
|
||||||
// Authenticate packet with noise_es_ee (first HMAC) before decrypting and parsing static info.
|
// Check the first HMAC to verify against the currently known noise_es_ee key, which verifies
|
||||||
let pkt_assembled_enc_end = pkt_assembled.len() - (HMAC_SHA384_SIZE * 2);
|
// that this reply is part of this session.
|
||||||
|
let auth_start = pkt_assembled.len() - ALICE_NOISE_XK_ACK_AUTH_SIZE;
|
||||||
if !secure_eq(
|
if !secure_eq(
|
||||||
&pkt_assembled[pkt_assembled_enc_end..pkt_assembled.len() - HMAC_SHA384_SIZE],
|
&pkt_assembled[auth_start..pkt_assembled.len() - HMAC_SHA384_SIZE],
|
||||||
&hmac_sha384_2(
|
&hmac_sha384_2(
|
||||||
&kbkdf512(noise_es_ee.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION).as_bytes()[..HMAC_SHA384_SIZE],
|
kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION>(incomplete.noise_es_ee.as_bytes())
|
||||||
&message_nonce,
|
.as_bytes(),
|
||||||
&pkt_assembled[HEADER_SIZE..pkt_assembled_enc_end],
|
&incoming_message_nonce,
|
||||||
|
&pkt_assembled[HEADER_SIZE..auth_start],
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
return Err(Error::FailedAuthentication);
|
return Err(Error::FailedAuthentication);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save a copy of the encrypted unmodified packet for final HMAC.
|
// Make a copy of pkt_assembled so we can check the second HMAC against original ciphertext later.
|
||||||
let mut pkt_saved_for_final_hmac = [0u8; NOISE_MAX_HANDSHAKE_PACKET_SIZE];
|
let mut pkt_assembly_buffer_copy = [0u8; MAX_NOISE_HANDSHAKE_SIZE];
|
||||||
pkt_saved_for_final_hmac[..pkt_assembled.len()].copy_from_slice(pkt_assembled);
|
pkt_assembly_buffer_copy[..pkt_assembled.len()].copy_from_slice(pkt_assembled);
|
||||||
|
|
||||||
// Decrypt Alice's static identity and decode.
|
// Decrypt encrypted section so we can finally learn Alice's static identity.
|
||||||
let mut ctr =
|
let mut ctr = AesCtr::new(
|
||||||
AesCtr::new(&kbkdf512(noise_es_ee.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION).as_bytes()[..AES_KEY_SIZE]);
|
kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION>(incomplete.noise_es_ee.as_bytes()).as_bytes(),
|
||||||
ctr.reset_set_iv(&SHA384::hash(hk.as_bytes())[..AES_CTR_NONCE_SIZE]);
|
);
|
||||||
ctr.crypt_in_place(&mut pkt_assembled[ALICE_NOISE_XK_ACK_ENC_START..pkt_assembled_enc_end]);
|
ctr.reset_set_iv(&incomplete.hk.as_bytes()[..AES_CTR_NONCE_SIZE]);
|
||||||
|
ctr.crypt_in_place(&mut pkt_assembled[ALICE_NOISE_XK_ACK_ENC_START..auth_start]);
|
||||||
|
|
||||||
let mut alice_static_info = &pkt_assembled[ALICE_NOISE_XK_ACK_ENC_START..pkt_assembled_enc_end];
|
// Read the static public blob and optional meta-data.
|
||||||
if alice_static_info.len() < 2 {
|
let mut pkt_assembled_ptr = HEADER_SIZE + 1;
|
||||||
|
let mut pkt_assembled_field_end = pkt_assembled_ptr + 2;
|
||||||
|
if pkt_assembled_field_end >= pkt_assembled.len() {
|
||||||
return Err(Error::InvalidPacket);
|
return Err(Error::InvalidPacket);
|
||||||
}
|
}
|
||||||
let alice_static_public_blob_len = u16::from_le_bytes(alice_static_info[..2].try_into().unwrap()) as usize;
|
let alice_static_public_blob_size =
|
||||||
alice_static_info = &alice_static_info[2..];
|
u16::from_le(memory::load_raw::<u16>(&pkt_assembled[pkt_assembled_ptr..pkt_assembled_field_end])) as usize;
|
||||||
if alice_static_info.len() < alice_static_public_blob_len {
|
pkt_assembled_ptr = pkt_assembled_field_end;
|
||||||
|
pkt_assembled_field_end = pkt_assembled_ptr + alice_static_public_blob_size;
|
||||||
|
if pkt_assembled_field_end >= pkt_assembled.len() {
|
||||||
return Err(Error::InvalidPacket);
|
return Err(Error::InvalidPacket);
|
||||||
}
|
}
|
||||||
let alice_static_public_blob = &alice_static_info[..alice_static_public_blob_len];
|
let alice_static_public_blob = &pkt_assembled[pkt_assembled_ptr..pkt_assembled_field_end];
|
||||||
alice_static_info = &alice_static_info[alice_static_public_blob_len..];
|
pkt_assembled_ptr = pkt_assembled_field_end;
|
||||||
if alice_static_info.len() < 2 {
|
pkt_assembled_field_end = pkt_assembled_ptr + 2;
|
||||||
|
if pkt_assembled_field_end >= pkt_assembled.len() {
|
||||||
return Err(Error::InvalidPacket);
|
return Err(Error::InvalidPacket);
|
||||||
}
|
}
|
||||||
let meta_data_len = u16::from_le_bytes(alice_static_info[..2].try_into().unwrap()) as usize;
|
let alice_meta_data_size =
|
||||||
alice_static_info = &alice_static_info[2..];
|
u16::from_le(memory::load_raw::<u16>(&pkt_assembled[pkt_assembled_ptr..pkt_assembled_field_end])) as usize;
|
||||||
if alice_static_info.len() < meta_data_len {
|
pkt_assembled_ptr = pkt_assembled_field_end;
|
||||||
return Err(Error::InvalidPacket);
|
pkt_assembled_field_end = pkt_assembled_ptr + alice_meta_data_size;
|
||||||
}
|
let alice_meta_data = if alice_meta_data_size > 0 {
|
||||||
let meta_data = &alice_static_info[..meta_data_len];
|
Some(&pkt_assembled[pkt_assembled_ptr..pkt_assembled_field_end])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
if let Some((bob_session_id, psk, app_data)) =
|
// Check session acceptance and fish Alice's NIST P-384 static public key out of
|
||||||
app.accept_new_session(self, remote_address, alice_static_public_blob, meta_data)
|
// her static public blob.
|
||||||
{
|
let check_result = check_accept_session(alice_static_public_blob, alice_meta_data);
|
||||||
// Create final Noise_XKpsk3 shared secret on this side.
|
if check_result.is_none() {
|
||||||
|
self.sessions.write().unwrap().incomplete.remove(&incomplete.bob_session_id);
|
||||||
|
return Ok(ReceiveResult::Rejected);
|
||||||
|
}
|
||||||
|
let (alice_noise_s, psk, application_data) = check_result.unwrap();
|
||||||
|
|
||||||
|
// Complete Noise_XKpsk3 on Bob's side.
|
||||||
let noise_es_ee_se_hk_psk = Secret(hmac_sha512(
|
let noise_es_ee_se_hk_psk = Secret(hmac_sha512(
|
||||||
&hmac_sha512(
|
&hmac_sha512(
|
||||||
noise_es_ee.as_bytes(),
|
incomplete.noise_es_ee.as_bytes(),
|
||||||
bob_noise_e_secret
|
incomplete
|
||||||
.agree(
|
.bob_noise_e_secret
|
||||||
&Application::extract_s_public_from_raw(alice_static_public_blob)
|
.agree(&alice_noise_s)
|
||||||
.ok_or(Error::FailedAuthentication)?,
|
|
||||||
)
|
|
||||||
.ok_or(Error::FailedAuthentication)?
|
.ok_or(Error::FailedAuthentication)?
|
||||||
.as_bytes(),
|
.as_bytes(),
|
||||||
),
|
),
|
||||||
&hmac_sha512(psk.as_bytes(), hk.as_bytes()),
|
&hmac_sha512(psk.as_bytes(), incomplete.hk.as_bytes()),
|
||||||
));
|
));
|
||||||
|
|
||||||
// Final authentication with the whole enchelada.
|
// Verify the packet using the final key to verify the whole key exchange.
|
||||||
if !secure_eq(
|
if !secure_eq(
|
||||||
&pkt_assembled[pkt_assembled_enc_end + HMAC_SHA384_SIZE..],
|
&pkt_assembly_buffer_copy[auth_start + HMAC_SHA384_SIZE..pkt_assembled.len()],
|
||||||
&hmac_sha384_2(
|
&hmac_sha384_2(
|
||||||
&kbkdf512(noise_es_ee_se_hk_psk.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION).as_bytes()
|
kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION>(noise_es_ee_se_hk_psk.as_bytes())
|
||||||
[..HMAC_SHA384_SIZE],
|
.as_bytes(),
|
||||||
&message_nonce,
|
&incoming_message_nonce,
|
||||||
&pkt_saved_for_final_hmac[HEADER_SIZE..pkt_assembled_enc_end + HMAC_SHA384_SIZE],
|
&pkt_assembly_buffer_copy[HEADER_SIZE..auth_start],
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
return Err(Error::FailedAuthentication);
|
return Err(Error::FailedAuthentication);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(ReceiveResult::OkNewSession(Arc::new(Session {
|
let session = Arc::new(Session {
|
||||||
id: bob_session_id,
|
id: incomplete.bob_session_id,
|
||||||
application_data: app_data,
|
application_data,
|
||||||
psk,
|
psk,
|
||||||
send_counter: AtomicU64::new(2), // 1 was already used in our first reply
|
send_counter: AtomicU64::new(2), // 1 was already used during negotiation
|
||||||
receive_window: std::array::from_fn(|_| AtomicU64::new(0)),
|
receive_window: std::array::from_fn(|_| AtomicU64::new(0)),
|
||||||
header_check_cipher,
|
header_protection_cipher: Aes::new(incomplete.header_protection_key.as_bytes()),
|
||||||
offer: Mutex::new(EphemeralOffer::None),
|
|
||||||
state: RwLock::new(State {
|
state: RwLock::new(State {
|
||||||
remote_session_id: Some(alice_session_id),
|
remote_session_id: Some(incomplete.alice_session_id),
|
||||||
keys: [Some(SessionKey::new(noise_es_ee_se_hk_psk, current_time, 2, true, true)), None],
|
keys: [
|
||||||
|
Some(SessionKey::new::<Application>(noise_es_ee_se_hk_psk, current_time, 2, true, true)),
|
||||||
|
None,
|
||||||
|
],
|
||||||
current_key: 0,
|
current_key: 0,
|
||||||
|
offer: EphemeralOffer::None,
|
||||||
}),
|
}),
|
||||||
defrag: Mutex::new(RingBufferMap::new(random::xorshift64_random() as u32)),
|
defrag: Mutex::new(RingBufferMap::new(random::xorshift64_random() as u32)),
|
||||||
})));
|
});
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut sessions = self.sessions.write().unwrap();
|
||||||
|
sessions.incomplete.remove(&incomplete.bob_session_id);
|
||||||
|
sessions.active.insert(incomplete.bob_session_id, Arc::downgrade(&session));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(ReceiveResult::OkNewSession(session));
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::NewSessionRejected);
|
return Err(Error::UnknownLocalSessionId);
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
todo!()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PACKET_TYPE_ALICE_REKEY_INIT => todo!(),
|
PACKET_TYPE_ALICE_REKEY_INIT => {
|
||||||
|
if pkt_assembled.len() != AliceRekeyInit::SIZE {
|
||||||
|
return Err(Error::InvalidPacket);
|
||||||
|
}
|
||||||
|
if let Some(session) = session {
|
||||||
|
let state = session.state.read().unwrap();
|
||||||
|
if let Some(key) = state.keys[key_index].as_ref() {
|
||||||
|
// Only the current "Alice" accepts rekeys initiated by the current "Bob."
|
||||||
|
if !key.role_is_bob {
|
||||||
|
let mut c = key.get_receive_cipher();
|
||||||
|
c.reset_init_gcm(&incoming_message_nonce);
|
||||||
|
c.crypt_in_place(&mut pkt_assembled[AliceRekeyInit::ENC_START..AliceRekeyInit::AUTH_START]);
|
||||||
|
let aead_authentication_ok = c.finish_decrypt(&pkt_assembled[AliceRekeyInit::AUTH_START..]);
|
||||||
|
key.return_receive_cipher(c);
|
||||||
|
|
||||||
PACKET_TYPE_BOB_REKEY_ACK => todo!(),
|
if aead_authentication_ok {
|
||||||
|
let pkt: &AliceRekeyInit = byte_array_as_proto_buffer(&pkt_assembled).unwrap();
|
||||||
|
if let Some(alice_e) = P384PublicKey::from_bytes(&pkt.alice_e) {
|
||||||
|
let bob_e_secret = P384KeyPair::generate();
|
||||||
|
let ee = bob_e_secret.agree(&alice_e).ok_or(Error::FailedAuthentication)?;
|
||||||
|
let next_session_key = Secret(hmac_sha512(key.ratchet_key.as_bytes(), ee.as_bytes()));
|
||||||
|
|
||||||
|
let mut reply_buf = [0u8; BobRekeyAck::SIZE];
|
||||||
|
let reply: &mut BobRekeyAck = byte_array_as_proto_buffer_mut(&mut reply_buf).unwrap();
|
||||||
|
reply.bob_e = *bob_e_secret.public_key_bytes();
|
||||||
|
reply.next_key_fingerprint = SHA384::hash(next_session_key.as_bytes());
|
||||||
|
|
||||||
|
let counter = session.get_next_outgoing_counter().ok_or(Error::MaxKeyLifetimeExceeded)?;
|
||||||
|
let mut c = key.get_send_cipher(counter.get())?;
|
||||||
|
c.reset_init_gcm(&create_message_nonce(PACKET_TYPE_BOB_REKEY_ACK, counter.get()));
|
||||||
|
c.crypt_in_place(&mut reply_buf[BobRekeyAck::ENC_START..BobRekeyAck::AUTH_START]);
|
||||||
|
reply_buf[BobRekeyAck::AUTH_START..].copy_from_slice(&c.finish_encrypt());
|
||||||
|
key.return_send_cipher(c);
|
||||||
|
|
||||||
|
send(Some(&session), &mut reply_buf);
|
||||||
|
|
||||||
|
drop(state);
|
||||||
|
let mut state = session.state.write().unwrap();
|
||||||
|
let _ = state.keys[key_index ^ 1].replace(SessionKey::new::<Application>(
|
||||||
|
next_session_key,
|
||||||
|
current_time,
|
||||||
|
counter.get(),
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
));
|
||||||
|
|
||||||
|
return Ok(ReceiveResult::Ok);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Err(Error::FailedAuthentication);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Err(Error::OutOfSequence);
|
||||||
|
} else {
|
||||||
|
return Err(Error::UnknownLocalSessionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PACKET_TYPE_BOB_REKEY_ACK => {
|
||||||
|
if pkt_assembled.len() != BobRekeyAck::SIZE {
|
||||||
|
return Err(Error::InvalidPacket);
|
||||||
|
}
|
||||||
|
if let Some(session) = session {
|
||||||
|
let state = session.state.read().unwrap();
|
||||||
|
if let EphemeralOffer::RekeyInit(alice_e_secret) = &state.offer {
|
||||||
|
if let Some(key) = state.keys[key_index].as_ref() {
|
||||||
|
// Only the current "Bob" initiates rekeys and expects this ACK.
|
||||||
|
if key.role_is_bob {
|
||||||
|
let mut c = key.get_receive_cipher();
|
||||||
|
c.reset_init_gcm(&incoming_message_nonce);
|
||||||
|
c.crypt_in_place(&mut pkt_assembled[BobRekeyAck::ENC_START..BobRekeyAck::AUTH_START]);
|
||||||
|
let aead_authentication_ok = c.finish_decrypt(&pkt_assembled[BobRekeyAck::AUTH_START..]);
|
||||||
|
key.return_receive_cipher(c);
|
||||||
|
|
||||||
|
if aead_authentication_ok {
|
||||||
|
let pkt: &BobRekeyAck = byte_array_as_proto_buffer(&pkt_assembled).unwrap();
|
||||||
|
if let Some(bob_e) = P384PublicKey::from_bytes(&pkt.bob_e) {
|
||||||
|
let ee = alice_e_secret.agree(&bob_e).ok_or(Error::FailedAuthentication)?;
|
||||||
|
let next_session_key = Secret(hmac_sha512(key.ratchet_key.as_bytes(), ee.as_bytes()));
|
||||||
|
|
||||||
|
if secure_eq(&pkt.next_key_fingerprint, &SHA384::hash(next_session_key.as_bytes())) {
|
||||||
|
drop(state);
|
||||||
|
let mut state = session.state.write().unwrap();
|
||||||
|
let _ = state.keys[key_index ^ 1].replace(SessionKey::new::<Application>(
|
||||||
|
next_session_key,
|
||||||
|
current_time,
|
||||||
|
counter,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
));
|
||||||
|
state.offer = EphemeralOffer::None;
|
||||||
|
|
||||||
|
return Ok(ReceiveResult::Ok);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Err(Error::FailedAuthentication);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Err(Error::OutOfSequence);
|
||||||
|
} else {
|
||||||
|
return Err(Error::UnknownLocalSessionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
return Err(Error::InvalidPacket);
|
return Err(Error::InvalidPacket);
|
||||||
|
@ -1027,8 +1176,8 @@ impl<Application: ApplicationLayer> Session<Application> {
|
||||||
mtu_sized_buffer[fragment_size..tagged_fragment_size].copy_from_slice(&c.finish_encrypt());
|
mtu_sized_buffer[fragment_size..tagged_fragment_size].copy_from_slice(&c.finish_encrypt());
|
||||||
fragment_size = tagged_fragment_size;
|
fragment_size = tagged_fragment_size;
|
||||||
}
|
}
|
||||||
self.header_check_cipher
|
self.header_protection_cipher
|
||||||
.encrypt_block_in_place(&mut mtu_sized_buffer[HEADER_CHECK_ENCRYPT_START..HEADER_CHECK_ENCRYPT_END]);
|
.encrypt_block_in_place(&mut mtu_sized_buffer[HEADER_PROTECT_ENCRYPT_START..HEADER_PROTECT_ENCRYPT_END]);
|
||||||
send(&mut mtu_sized_buffer[..fragment_size]);
|
send(&mut mtu_sized_buffer[..fragment_size]);
|
||||||
}
|
}
|
||||||
debug_assert!(data.is_empty());
|
debug_assert!(data.is_empty());
|
||||||
|
@ -1047,6 +1196,36 @@ impl<Application: ApplicationLayer> Session<Application> {
|
||||||
state.keys[state.current_key].is_some()
|
state.keys[state.current_key].is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Send a rekey init message.
|
||||||
|
///
|
||||||
|
/// This is called from the session context's service() method when it's time to rekey.
|
||||||
|
/// It should only be called when the current key was established in the 'bob' role. This
|
||||||
|
/// is checked when rekey time is checked.
|
||||||
|
fn send_rekey<SendFunction: FnMut(&mut [u8])>(&self, mut send: SendFunction) {
|
||||||
|
let rekey_e = P384KeyPair::generate();
|
||||||
|
|
||||||
|
let mut rekey_buf = [0u8; AliceRekeyInit::SIZE];
|
||||||
|
let pkt: &mut AliceRekeyInit = byte_array_as_proto_buffer_mut(&mut rekey_buf).unwrap();
|
||||||
|
pkt.alice_e = *rekey_e.public_key_bytes();
|
||||||
|
|
||||||
|
let state = self.state.read().unwrap();
|
||||||
|
if let Some(key) = state.keys[state.current_key].as_ref() {
|
||||||
|
if let Some(counter) = self.get_next_outgoing_counter() {
|
||||||
|
if let Ok(mut gcm) = key.get_send_cipher(counter.get()) {
|
||||||
|
gcm.reset_init_gcm(&create_message_nonce(PACKET_TYPE_ALICE_REKEY_INIT, counter.get()));
|
||||||
|
gcm.crypt_in_place(&mut rekey_buf[AliceRekeyInit::ENC_START..AliceRekeyInit::AUTH_START]);
|
||||||
|
rekey_buf[AliceRekeyInit::AUTH_START..].copy_from_slice(&gcm.finish_encrypt());
|
||||||
|
key.return_send_cipher(gcm);
|
||||||
|
|
||||||
|
send(&mut rekey_buf);
|
||||||
|
|
||||||
|
drop(state);
|
||||||
|
self.state.write().unwrap().offer = EphemeralOffer::RekeyInit(rekey_e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the next outgoing counter value.
|
/// Get the next outgoing counter value.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn get_next_outgoing_counter(&self) -> Option<NonZeroU64> {
|
fn get_next_outgoing_counter(&self) -> Option<NonZeroU64> {
|
||||||
|
@ -1142,7 +1321,7 @@ fn send_with_fragmentation<SendFunction: FnMut(&mut [u8])>(
|
||||||
recipient_session_id: Option<SessionId>,
|
recipient_session_id: Option<SessionId>,
|
||||||
key_index: usize,
|
key_index: usize,
|
||||||
counter: u64,
|
counter: u64,
|
||||||
header_check_cipher: Option<&Aes>,
|
header_protect_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 recipient_session_id = recipient_session_id.map_or(SessionId::NONE, |s| u64::from(s));
|
||||||
|
@ -1160,8 +1339,8 @@ fn send_with_fragmentation<SendFunction: FnMut(&mut [u8])>(
|
||||||
key_index,
|
key_index,
|
||||||
counter,
|
counter,
|
||||||
);
|
);
|
||||||
if let Some(hcc) = header_check_cipher {
|
if let Some(hcc) = header_protect_cipher {
|
||||||
hcc.encrypt_block_in_place(&mut fragment[6..22]);
|
hcc.encrypt_block_in_place(&mut fragment[HEADER_PROTECT_ENCRYPT_START..HEADER_PROTECT_ENCRYPT_END]);
|
||||||
}
|
}
|
||||||
send(fragment);
|
send(fragment);
|
||||||
fragment_start = fragment_end - HEADER_SIZE;
|
fragment_start = fragment_end - HEADER_SIZE;
|
||||||
|
@ -1171,7 +1350,13 @@ fn send_with_fragmentation<SendFunction: FnMut(&mut [u8])>(
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SessionKey {
|
impl SessionKey {
|
||||||
fn new(key: Secret<BASE_KEY_SIZE>, current_time: i64, current_counter: u64, confirmed: bool, role_is_bob: bool) -> Self {
|
fn new<Application: ApplicationLayer>(
|
||||||
|
key: Secret<BASE_KEY_SIZE>,
|
||||||
|
current_time: i64,
|
||||||
|
current_counter: u64,
|
||||||
|
confirmed: bool,
|
||||||
|
role_is_bob: bool,
|
||||||
|
) -> Self {
|
||||||
let a2b = kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB>(key.as_bytes());
|
let a2b = kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB>(key.as_bytes());
|
||||||
let b2a = kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE>(key.as_bytes());
|
let b2a = kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE>(key.as_bytes());
|
||||||
let (receive_key, send_key) = if role_is_bob {
|
let (receive_key, send_key) = if role_is_bob {
|
||||||
|
@ -1186,11 +1371,14 @@ impl SessionKey {
|
||||||
receive_cipher_pool: Mutex::new(Vec::with_capacity(2)),
|
receive_cipher_pool: Mutex::new(Vec::with_capacity(2)),
|
||||||
send_cipher_pool: Mutex::new(Vec::with_capacity(2)),
|
send_cipher_pool: Mutex::new(Vec::with_capacity(2)),
|
||||||
rekey_at_time: current_time
|
rekey_at_time: current_time
|
||||||
.checked_add(REKEY_AFTER_TIME_MS + ((random::xorshift64_random() as u32) % REKEY_AFTER_TIME_MS_MAX_JITTER) as i64)
|
.checked_add(
|
||||||
|
Application::REKEY_AFTER_TIME_MS
|
||||||
|
+ ((random::xorshift64_random() as u32) % Application::REKEY_AFTER_TIME_MS_MAX_JITTER) as i64,
|
||||||
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
created_at_counter: current_counter,
|
created_at_counter: current_counter,
|
||||||
rekey_at_counter: current_counter.checked_add(REKEY_AFTER_USES).unwrap(),
|
rekey_at_counter: current_counter.checked_add(Application::REKEY_AFTER_USES).unwrap(),
|
||||||
expire_at_counter: current_counter.checked_add(EXPIRE_AFTER_USES).unwrap(),
|
expire_at_counter: current_counter.checked_add(Application::EXPIRE_AFTER_USES).unwrap(),
|
||||||
confirmed,
|
confirmed,
|
||||||
role_is_bob,
|
role_is_bob,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue