mirror of
https://github.com/ZeroTier/ZeroTierOne
synced 2025-08-22 14:23:59 -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"
|
||||
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]
|
||||
zerotier-utils = { path = "../utils" }
|
||||
zerotier-crypto = { path = "../crypto" }
|
||||
|
|
|
@ -6,32 +6,59 @@
|
|||
* 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.
|
||||
///
|
||||
/// Templating the session on this trait lets the code here be almost entirely transport, OS,
|
||||
/// 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 {
|
||||
/// 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;
|
||||
|
||||
/// 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.
|
||||
/// It can also just be a Vec<u8> or Box<[u8]> or something like that.
|
||||
/// This can be something like Vec<u8> or Box<[u8]> or it can be something like a pooled reusable
|
||||
/// 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]>;
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// 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/
|
||||
*/
|
||||
|
||||
use crate::sessionid::SessionId;
|
||||
|
||||
pub enum Error {
|
||||
/// The packet was addressed to an unrecognized local session (should usually be ignored)
|
||||
UnknownLocalSessionId(SessionId),
|
||||
UnknownLocalSessionId,
|
||||
|
||||
/// Packet was not well formed
|
||||
InvalidPacket,
|
||||
|
@ -29,12 +27,6 @@ pub enum Error {
|
|||
/// Attempt to send using session without established key
|
||||
SessionNotEstablished,
|
||||
|
||||
/// Packet ignored by rate limiter.
|
||||
RateLimited,
|
||||
|
||||
/// Packet counter is too far outside window.
|
||||
OutOfCounterWindow,
|
||||
|
||||
/// The other peer specified an unrecognized protocol version
|
||||
UnknownProtocolVersion,
|
||||
|
||||
|
@ -44,6 +36,9 @@ pub enum Error {
|
|||
/// Data object is too large to send, even with fragmentation
|
||||
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.
|
||||
///
|
||||
/// 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 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::UnknownLocalSessionId(id) => f.write_str(format!("UnknownLocalSessionId({})", id).as_str()),
|
||||
Self::InvalidPacket => f.write_str("InvalidPacket"),
|
||||
Self::InvalidParameter => f.write_str("InvalidParameter"),
|
||||
Self::FailedAuthentication => f.write_str("FailedAuthentication"),
|
||||
Self::MaxKeyLifetimeExceeded => f.write_str("MaxKeyLifetimeExceeded"),
|
||||
Self::SessionNotEstablished => f.write_str("SessionNotEstablished"),
|
||||
Self::RateLimited => f.write_str("RateLimited"),
|
||||
Self::OutOfCounterWindow => f.write_str("OutOfCounterWindow"),
|
||||
Self::UnknownProtocolVersion => f.write_str("UnknownProtocolVersion"),
|
||||
Self::DataBufferTooSmall => f.write_str("DataBufferTooSmall"),
|
||||
Self::DataTooLarge => f.write_str("DataTooLarge"),
|
||||
Self::UnexpectedBufferOverrun => f.write_str("UnexpectedBufferOverrun"),
|
||||
}
|
||||
f.write_str(match self {
|
||||
Self::UnknownLocalSessionId => "UnknownLocalSessionId",
|
||||
Self::InvalidPacket => "InvalidPacket",
|
||||
Self::InvalidParameter => "InvalidParameter",
|
||||
Self::FailedAuthentication => "FailedAuthentication",
|
||||
Self::MaxKeyLifetimeExceeded => "MaxKeyLifetimeExceeded",
|
||||
Self::SessionNotEstablished => "SessionNotEstablished",
|
||||
Self::UnknownProtocolVersion => "UnknownProtocolVersion",
|
||||
Self::DataBufferTooSmall => "DataBufferTooSmall",
|
||||
Self::DataTooLarge => "DataTooLarge",
|
||||
Self::OutOfSequence => "OutOfSequence",
|
||||
Self::UnexpectedBufferOverrun => "UnexpectedBufferOverrun",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,9 +13,8 @@ mod sessionid;
|
|||
mod tests;
|
||||
mod zssp;
|
||||
|
||||
pub mod constants;
|
||||
|
||||
pub use crate::applicationlayer::ApplicationLayer;
|
||||
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::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 crate::applicationlayer::ApplicationLayer;
|
||||
use crate::constants::*;
|
||||
use crate::error::Error;
|
||||
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 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 HEADER_SIZE: usize = 16;
|
||||
pub(crate) const HEADER_CHECK_ENCRYPT_START: usize = 6;
|
||||
pub(crate) const HEADER_CHECK_ENCRYPT_END: usize = 22;
|
||||
pub(crate) const HEADER_PROTECT_ENCRYPT_START: usize = 6;
|
||||
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_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 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_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))
|
||||
pub alice_session_id: [u8; SessionId::SIZE],
|
||||
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
|
||||
pub hmac_es: [u8; HMAC_SHA384_SIZE],
|
||||
}
|
||||
|
||||
impl AliceNoiseXKInit {
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -100,7 +108,7 @@ impl BobNoiseXKAck {
|
|||
pub(crate) struct AliceNoiseXKAck {
|
||||
pub header: [u8; HEADER_SIZE],
|
||||
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: [u8; ???],
|
||||
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)]
|
||||
#[repr(C, packed)]
|
||||
pub(crate) struct AliceRekeyInit {
|
||||
pub header: [u8; HEADER_SIZE],
|
||||
// -- 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
|
||||
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)]
|
||||
#[repr(C, packed)]
|
||||
pub(crate) struct BobRekeyAck {
|
||||
pub header: [u8; HEADER_SIZE],
|
||||
// -- start AES-GCM encrypted portion (using current key)
|
||||
pub bob_noise_e: [u8; P384_PUBLIC_KEY_SIZE],
|
||||
pub ee_fingerprint: [u8; SHA384_HASH_SIZE],
|
||||
pub bob_e: [u8; P384_PUBLIC_KEY_SIZE],
|
||||
pub next_key_fingerprint: [u8; SHA384_HASH_SIZE],
|
||||
// -- end AES-GCM encrypted portion
|
||||
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.
|
||||
pub(crate) fn assemble_fragments_into<A: ApplicationLayer>(fragments: &[A::IncomingPacketBuffer], d: &mut [u8]) -> Result<usize, Error> {
|
||||
let mut l = 0;
|
||||
|
|
648
zssp/src/zssp.rs
648
zssp/src/zssp.rs
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue