mirror of
https://github.com/zerotier/zssp.git
synced 2026-05-22 16:28:40 -07:00
Merge pull request #1 from zerotier/dev
Reformatting the API to match the whitepaper
This commit is contained in:
+1
-1
@@ -3,7 +3,7 @@ authors = ["ZeroTier, Inc. <contact@zerotier.com>", "Adam Ierymenko <adam.ieryme
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
name = "zssp"
|
||||
version = "0.0.2"
|
||||
version = "0.0.3"
|
||||
|
||||
[lib]
|
||||
name = "zssp"
|
||||
|
||||
@@ -1,21 +1,60 @@
|
||||
ZeroTier Secure Socket Protocol
|
||||
ZeroTier Secure Sessions Protocol
|
||||
======
|
||||
|
||||
# Introduction
|
||||
## Introduction
|
||||
|
||||
ZeroTier Secure Socket Protocol (ZSSP) is a [Noise](http://noiseprotocol.org) protocol implementation using NIST/FIPS/CfSC compliant cryptographic primitives plus post-quantum forward secrecy via [Kyber1024](https://pq-crystals.org/kyber/). It also includes built-in support for fragmentation and defragmentation of large messages with strong resistance against denial of service attacks targeted against the fragmentation protocol.
|
||||
|
||||
Specifically ZSSP implements the [Noise XK](http://noiseprotocol.org/noise.html#interactive-handshake-patterns-fundamental) interactive handshake pattern which provides strong forward secrecy not only for data but for the identities of the two participants in the sesssion. The XK pattern was chosen instead of the more popular IK pattern used in popular Noise implementations like Wireguard due to ZeroTier identities being long lived and potentially tied to the real world identity of the user. As a result a Noise pattern providing identity forward secrecy was considered preferable as it offers some level of deniability for recorded traffic even after secrec key compromise.
|
||||
Specifically ZSSP implements the [Noise XK](http://noiseprotocol.org/noise.html#interactive-handshake-patterns-fundamental) interactive handshake pattern which provides strong forward secrecy not only for data but for the identities of the two participants in the session. The XK pattern was chosen instead of the more popular IK pattern used in popular Noise implementations like Wireguard due to ZeroTier identities being long lived and potentially tied to the real world identity of the user. As a result a Noise pattern providing identity forward secrecy was considered preferable as it offers some level of deniability for recorded traffic even after secret key compromise.
|
||||
|
||||
Hybrid post-quantum forward secrecy using Kyber1024 is performed alongside Noise with the result being mixed in alongside an optional pre-shared key at the end of session negotiation.
|
||||
|
||||
ZSSP is designed for use in ZeroTier 2 but is payload-agnostic and could easily be adapted for use in other projects.
|
||||
ZSSP is designed for use in ZeroTier but is payload-agnostic and could easily be adapted for use in other projects.
|
||||
|
||||
Further information can be found in the ZSSP whitepaper (pending official release).
|
||||
|
||||
## Cryptographic Primitives Used
|
||||
|
||||
- AES-256-GCM: Authenticated encryption
|
||||
- HMAC-SHA384: Key mixing, sub-key derivation in key-based KDF construction
|
||||
- NIST P-384 ECDH: Elliptic curve key exchange during initial handshake and for periodic re-keying during the session
|
||||
- Kyber1024: Quantum attack resistant lattice-based key exchange during initial handshake
|
||||
- AES-256-ECB: Single 128-bit block encryption of header information to harden the fragmentation protocol against denial of service attack (see section on header protection)
|
||||
- **NIST P-384 ECDH**: Elliptic curve key exchange during initial handshake and for periodic re-keying during the session
|
||||
- **Kyber1024**: Quantum attack resistant lattice-based key exchange during initial handshake
|
||||
- **SHA-512**: Used to construct KBKDF, also used in a proof of work and IP ownership DOS mitigation scheme
|
||||
- **KBKDF**: Key mixing, sub-key derivation
|
||||
- **AES-256**: 128-bit PRP for AES-256-GCM and for authenticated encryption of header to harden fragmentation against DOS (see section on header protection)
|
||||
- **AES-256-GCM**: Authenticated encryption
|
||||
|
||||
## Security Properties
|
||||
|
||||
| | Persistent ZSSP | Opportunistic ZSSP| WireGuard | ZeroTier Legacy Transport |
|
||||
| --- | --- | --- | --- | --- |
|
||||
|**Construction**|Noise\_XKhfs+psk2|Noise\_XKhfs+psk2|Noise\_IKpsk2|Static Diffie-Helman|
|
||||
|**Perfect Forward Secrecy**|Yes|Yes|Yes|No|
|
||||
|**Forward Secret Identity Hiding**|Yes|Yes|No|No|
|
||||
|**Quantum Forward Secret**|Yes|Yes|No|No|
|
||||
|**Ratcheted Forward Secrecy**|Yes|Yes|No|No|
|
||||
|**Silence is a Virtue**|Yes|No|Yes|No|
|
||||
|**Key-Compromise Impersonation**|Resistant|Resistant|Resistant|Vulnerable|
|
||||
|**Compromise-and-Impersonate**|Resistant|Detectable|Vulnerable|Vulnerable|
|
||||
|**Single Key-Compromise MitM**|Resistant|Resistant|Resistant|Vulnerable|
|
||||
|**Double Key-Compromise MitM**|Resistant|Detectable|Vulnerable|Vulnerable|
|
||||
|**DOS Mitigation**|Yes|Yes|Yes|No|
|
||||
|**Supports Fragmentation**|Yes|Yes|No|Yes|
|
||||
|**FIPS Compliant**|Yes|Yes|No|No|
|
||||
|**Small Code Footprint**|Yes|Yes|Yes|No|
|
||||
|**RTT**|2|2|1|1|
|
||||
|
||||
### Definitions
|
||||
|
||||
* **Construction**: The mathematical construction the protocol is based upon.
|
||||
* **Perfect Forward Secrecy**: An attacker with the static private keys of both party cannot decrypt recordings of messages sent between those parties.
|
||||
* **Forward Secret Identity Hiding**: An attacker with the static private key of one or more parties cannot determine the identity of everyone they have previously communicated with.
|
||||
* **Quantum Forward Secret**: A quantum computer powerful enough to break Elliptic-curve cryptography is not sufficient in order to decrypt recordings of messages sent between parties.
|
||||
* **Ratcheted Forward Secrecy**: In order to break forward secrecy an attacker must record and break every single key exchange two parties perform, in order, starting from the first time they began communicating. Improves secrecy under weak or compromised RNG.
|
||||
* **Key-Compromise Impersonation**: The attacker has a memory image of a single party, and attempts to create a brand new session with that party, pretending to be someone else.
|
||||
* **Compromise-and-Impersonate**: The attacker has a memory image of a single party, and attempts to impersonate them on a brand new session with the other party.
|
||||
* **Single Key-Compromise MitM**: The attacker has a memory image of a single party, and attempts to become a Man-in-the-Middle between them and any other party.
|
||||
* **Double Key-Compromise MitM**: The attacker has a memory image of both parties, and attempts to become a Man-in-the-Middle between them.
|
||||
* **Silence is a Virtue**: A server running the protocol can be configured in such a way that it will not respond to an unauthenticated, anonymous or replayed message.
|
||||
* **Supports Fragmentation**: Transmission data can be fragmented into smaller units to support jumbo-sized data or MTU discovery.
|
||||
* **FIPS Compliant**: The protocol uses FIPS approved cryptographic algorithms.
|
||||
* **Small Code Footprint**: The Codebase implementing the protocol can be easily audited by anyone on the internet.
|
||||
* **RTT**: "Round-Trip-Time" - How many round trips from initiator to responder it takes to establish a session.
|
||||
|
||||
+39
-126
@@ -5,15 +5,15 @@
|
||||
* (c) ZeroTier, Inc.
|
||||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::crypto::aes::{AesDec, AesEnc};
|
||||
use crate::crypto::aes_gcm::{AesGcmDec, AesGcmEnc};
|
||||
use crate::crypto::p384::{P384KeyPair, P384PublicKey};
|
||||
use crate::crypto::sha512::{HmacSha512, Sha512};
|
||||
use crate::crypto::rand_core::{CryptoRng, RngCore};
|
||||
use crate::{log_event::LogEvent, Session, RATCHET_FINGERPRINT_SIZE, RATCHET_KEY_SIZE};
|
||||
use crate::crypto::sha512::{HmacSha512, Sha512};
|
||||
use crate::RatchetState;
|
||||
use crate::{log_event::LogEvent, Session, RATCHET_SIZE};
|
||||
|
||||
/// Trait to implement to integrate the session into an application.
|
||||
///
|
||||
@@ -98,6 +98,8 @@ pub trait ApplicationLayer: Sized {
|
||||
type PublicKey: P384PublicKey;
|
||||
type KeyPair: P384KeyPair<PublicKey = Self::PublicKey, Rng = Self::Rng>;
|
||||
|
||||
type IoError: std::fmt::Debug;
|
||||
|
||||
/// Type for arbitrary opaque object for use by the application that is attached to
|
||||
/// each session.
|
||||
type Data;
|
||||
@@ -114,25 +116,22 @@ pub trait ApplicationLayer: Sized {
|
||||
/// It will be dropped as soon as the session is established.
|
||||
type LocalIdentityBlob: AsRef<[u8]>;
|
||||
|
||||
/// This function will be called whenever Alice's initial Hello packet contains the zero ratchet
|
||||
/// fingerprint. Brand new peers will always connect to Bob with the zero ratchet key, but from
|
||||
/// then on they should be using non-zero ratchet keys.
|
||||
/// This function will be called whenever Alice's initial Hello packet contains the empty ratchet
|
||||
/// fingerprint. Brand new peers will always connect to Bob with the empty ratchet, but from
|
||||
/// then on they should be using non-empty ratchet states.
|
||||
///
|
||||
/// If this returns false, we will attempt to connect to Alice with the zero ratchet key.
|
||||
/// If this returns false, we will attempt to connect to Alice with the empty ratchet state.
|
||||
/// If this returns true, Alice's connection will be silently dropped.
|
||||
/// If this function is configured to always return true, it means peers will not be able to
|
||||
/// connect to us unless they had a prior-established ratchet key with us. This is the best way
|
||||
/// for the paranoid to enforce a manual allow-list.
|
||||
#[allow(unused)]
|
||||
fn hello_requires_recognized_ratchet(&self, current_time: i64) -> bool {
|
||||
false
|
||||
}
|
||||
fn hello_requires_recognized_ratchet(&self, current_time: i64) -> bool;
|
||||
/// This function is called if we, as Alice, attempted to open a session with Bob using a
|
||||
/// non-zero ratchet key, but Bob does not have this ratchet key and wants to downgrade
|
||||
/// non-empty ratchet key, but Bob does not have this ratchet key and wants to downgrade
|
||||
/// to the zero ratchet key.
|
||||
///
|
||||
/// If it returns true Alice will downgrade their ratchet number to 0, potentially ending their
|
||||
/// current ratchet chain.
|
||||
/// If it returns true Alice will downgrade their ratchet state to emtpy, potentially ending
|
||||
/// their current ratchet chain.
|
||||
/// If it returns false then we will consider Bob as having failed authentication, and this
|
||||
/// packet will be dropped. The session will continue attempting to connect to Bob.
|
||||
///
|
||||
@@ -142,15 +141,9 @@ pub trait ApplicationLayer: Sized {
|
||||
/// least one party is misconfigured and got their ratchet keys corrupted or lost, or Bob has
|
||||
/// been compromised and is being impersonated. An attacker must at least have Bob's private
|
||||
/// static key to be able to ask Alice to downgrade.
|
||||
///
|
||||
/// If Alice does decide to reconnect without a ratchet key, be sure to generate some warning
|
||||
/// that something has gone wrong and Bob could not be fully authenticated.
|
||||
#[allow(unused)]
|
||||
fn initiator_disallows_downgrade(&self, session: &Arc<Session<Self>>, current_time: i64) -> bool {
|
||||
false
|
||||
}
|
||||
/// Lookup a specific ratchet key based on its ratchet fingerprint.
|
||||
/// This function will be called whenever Alice attempts to connect to us with a non-zero
|
||||
fn initiator_disallows_downgrade(&self, session: &Arc<Session<Self>>, current_time: i64) -> bool;
|
||||
/// Lookup a specific ratchet state based on its ratchet fingerprint.
|
||||
/// This function will be called whenever Alice attempts to connect to us with a non-empty
|
||||
/// ratchet fingerprint.
|
||||
///
|
||||
/// If the ratchet key was found, the function should return `RestoreAction::RestoreRatchet`. This will
|
||||
@@ -159,125 +152,45 @@ pub trait ApplicationLayer: Sized {
|
||||
/// If the ratchet key could not be found, the application may choose between returning
|
||||
/// `RatchetAction::DowngradeRatchet` or `RatchetAction::FailAuthentication`.
|
||||
/// If `RatchetAction::DowngradeRatchet` is returned we will attempt to convince Alice to downgrade
|
||||
/// to the zero ratchet key, restarting the ratchet chain.
|
||||
/// to the empty ratchet key, restarting the ratchet chain.
|
||||
/// If `RatchetAction::FailAuthentication` is returned Alice's connection will be silently dropped.
|
||||
#[allow(unused)]
|
||||
fn restore_ratchet(&self, ratchet_fingerprint: &[u8; RATCHET_FINGERPRINT_SIZE], current_time: i64) -> Result<RestoreAction, ()> {
|
||||
Ok(RestoreAction::DowngradeRatchet)
|
||||
}
|
||||
/// Atomically save the given ratchet key, fingerprint and number to persistent storage.
|
||||
fn restore_by_fingerprint(&self, ratchet_fingerprint: &[u8; RATCHET_SIZE], current_time: i64) -> Result<RatchetState, Self::IoError>;
|
||||
|
||||
/// Lookup a specific ratchet state based on the identity of the peer being communicated with.
|
||||
/// This function will be called whenever Alice attempts to open a session, or Bob attempts
|
||||
/// to verify Alice's identity.
|
||||
fn restore_by_identity(
|
||||
&self,
|
||||
remote_static_key: &Self::PublicKey,
|
||||
application_data: &Self::Data,
|
||||
current_time: i64,
|
||||
) -> Result<[RatchetState; 2], Self::IoError>;
|
||||
/// Atomically save the given `new_ratchet_states` to persistent storage.
|
||||
/// `pre_ratchet_states` contains what should be the previous contents of persistent storage.
|
||||
///
|
||||
/// See the documentation of `SaveAction` for more details on how to save them to storage,
|
||||
/// and how to handle any pre-existing ratchet keys, fingerprints and numbers.
|
||||
///
|
||||
/// If this returns `Err(())`, the packet which triggered this function to be called will be
|
||||
/// If this returns `Err(IoError)`, the packet which triggered this function to be called will be
|
||||
/// dropped, and no session state will be mutated, preserving synchronization. The remote peer
|
||||
/// will eventually resend that packet and so this function will be called again.
|
||||
///
|
||||
/// If persistent storage is supported, this function should not return until the ratchet state
|
||||
/// is saved, otherwise it is possible, albeit unlikely, for a sudden restart of the local
|
||||
/// machine to put our ratchet state out of sync with the remote peer. If this happens the only
|
||||
/// fix is to reset both ratchet keys to zero.
|
||||
/// fix is to reset both ratchet keys to empty.
|
||||
///
|
||||
/// This function may also save state to volatile storage, in which case all peers which connect
|
||||
/// to us will have to allow downgrade
|
||||
/// (`initiator_disallows_downgrade` returns false and/or`restore_ratchet` returns `DowngradeRatchet`).
|
||||
/// to us will have to allow downgrade, i.e. `initiator_disallows_downgrade` returns false
|
||||
/// and/or `check_accept_session` returns `(Some(true, _), _)`.
|
||||
/// Otherwise, when we restart, we will not be allowed to reconnect.
|
||||
#[allow(unused)]
|
||||
fn save_ratchet_state(
|
||||
&self,
|
||||
remote_static_key: &Self::PublicKey,
|
||||
application_data: &Self::Data,
|
||||
ratchet_action: SaveAction,
|
||||
latest_ratchet_number: u64,
|
||||
latest_ratchet_fingerprint: &[u8; RATCHET_FINGERPRINT_SIZE],
|
||||
latest_ratchet_key: &[u8; RATCHET_KEY_SIZE],
|
||||
pre_ratchet_states: [&RatchetState; 2],
|
||||
new_ratchet_states: [&RatchetState; 2],
|
||||
current_time: i64,
|
||||
) -> Result<(), ()> {
|
||||
Ok(())
|
||||
}
|
||||
) -> Result<(), Self::IoError>;
|
||||
|
||||
#[allow(unused)]
|
||||
#[inline]
|
||||
fn event_log<'a>(&self, event: LogEvent<'a, Self>, current_time: i64) {}
|
||||
}
|
||||
pub enum RestoreAction {
|
||||
RestoreRatchet(u64, [u8; RATCHET_KEY_SIZE]),
|
||||
DowngradeRatchet,
|
||||
FailAuthentication,
|
||||
}
|
||||
/// Ratchet keys and fingerprints should be saved *per remote peer*. It is up to the application to
|
||||
/// enforce separate storage for each remote peer based on `remote_static_key` and `application_data`.
|
||||
///
|
||||
/// Only up to 2 ratchet keys and fingerprints may be saved at one time.
|
||||
/// If a 3rd needs to be saved the 1st should be deleted, if it was not already deleted.
|
||||
pub enum SaveAction {
|
||||
/// Save the given `latest_ratchet_fingerprint` and `latest_ratchet_key`,
|
||||
/// but do not update the confirmed ratchet number.
|
||||
///
|
||||
/// If a ratchet key and fingerprint already exist with ratchet number `latest_ratchet_number`,
|
||||
/// then `latest_ratchet_fingerprint` and `latest_ratchet_key` should overwrite them.
|
||||
///
|
||||
/// Keep the previous ratchet state saved and searchable until it is explicitly deleted.
|
||||
/// If there are two saved ratchet keys and fingerprints, replace the oldest pair with
|
||||
/// the new pair.
|
||||
SaveAsUnconfirmed,
|
||||
/// Save the given `latest_ratchet_fingerprint` and `latest_ratchet_key`,
|
||||
/// and set the confirmed ratchet number to `latest_ratchet_number`.
|
||||
/// The confirmed ratchet number should be set equal to `latest_ratchet_number`.
|
||||
///
|
||||
/// If a ratchet key and fingerprint already exist with ratchet number `latest_ratchet_number`,
|
||||
/// then `latest_ratchet_fingerprint` and `latest_ratchet_key` should overwrite them.
|
||||
///
|
||||
/// Keep the previous ratchet state saved and searchable until it is explicitly deleted.
|
||||
/// If there are two saved ratchet keys and fingerprints, replace the oldest pair with
|
||||
/// the new pair.
|
||||
SaveAsConfirmed,
|
||||
/// Set the confirmed ratchet number to `latest_ratchet_number`,
|
||||
/// and permanently delete the previous (oldest) ratchet key and fingerprint.
|
||||
///
|
||||
/// The given `latest_ratchet_fingerprint` and `latest_ratchet_key` will be identical to those
|
||||
/// saved during a prior `SaveAsUnconfirmed` call.
|
||||
/// These two must be the only saved ratchet key and fingerprint when this call completes.
|
||||
ConfirmLatestAndDeletePrevious,
|
||||
/// Permanently delete the previous (oldest) ratchet key and fingerprint.
|
||||
///
|
||||
/// The given `latest_ratchet_fingerprint` and `latest_ratchet_key` will be identical to those
|
||||
/// saved during a prior `SaveAsConfirmed` call.
|
||||
/// These two must be the only saved ratchet key and fingerprint when this call completes.
|
||||
DeletePrevious,
|
||||
}
|
||||
use SaveAction::*;
|
||||
impl SaveAction {
|
||||
/// If this is true then the given `latest_ratchet_fingerprint` and `latest_ratchet_key`
|
||||
/// must be saved to permanent storage.
|
||||
/// `latest_ratchet_key` should be searchable using `latest_ratchet_fingerprint`.
|
||||
pub fn save_latest(&self) -> bool {
|
||||
match self {
|
||||
SaveAsUnconfirmed => true,
|
||||
SaveAsConfirmed => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
/// If this is true then the previous ratchet key and fingerprint should be deleted.
|
||||
/// If `latest_ratchet_number > 1`, then the previous ratchet key and fingerprint will have
|
||||
/// ratchet number `latest_ratchet_number - 1`.
|
||||
pub fn delete_previous(&self) -> bool {
|
||||
match self {
|
||||
ConfirmLatestAndDeletePrevious => true,
|
||||
DeletePrevious => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
/// If this is true then set the confirmed ratchet number to `latest_ratchet_number`.
|
||||
///
|
||||
/// The confirmed ratchet number is a single 64-bit number saved to persistent storage that denotes its
|
||||
/// associated ratchet key is "confirmed". Only the confirmed ratchet key should be used to
|
||||
/// `open` a new session.
|
||||
pub fn confirm_latest(&self) -> bool {
|
||||
match self {
|
||||
ConfirmLatestAndDeletePrevious => true,
|
||||
SaveAsConfirmed => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
fn event_log(&self, event: LogEvent<Self>, current_time: i64) {}
|
||||
}
|
||||
|
||||
@@ -6,5 +6,7 @@ pub mod p384;
|
||||
pub mod secret;
|
||||
pub mod sha512;
|
||||
|
||||
// We re-export our dependencies so it is less of a headache for the implementor to use the same
|
||||
// exact version of them.
|
||||
pub use pqc_kyber;
|
||||
pub use rand_core;
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md.
|
||||
|
||||
use super::rand_core::{RngCore, CryptoRng};
|
||||
use super::rand_core::{CryptoRng, RngCore};
|
||||
|
||||
pub const P384_PUBLIC_KEY_SIZE: usize = 49;
|
||||
pub const P384_ECDH_SHARED_SECRET_SIZE: usize = 48;
|
||||
|
||||
+6
-12
@@ -1,5 +1,4 @@
|
||||
// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md.
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
||||
/// Constant time byte slice equality.
|
||||
@@ -36,20 +35,16 @@ impl<const L: usize> Secret<L> {
|
||||
pub fn new() -> Self {
|
||||
Self([0_u8; L])
|
||||
}
|
||||
|
||||
/// Moves bytes into secret, will panic if the slice does not match the size of this secret.
|
||||
#[inline(always)]
|
||||
pub fn move_bytes(b: [u8; L]) -> Self {
|
||||
Self(b)
|
||||
}
|
||||
|
||||
/// Copy bytes into secret, then nuke the previous value, will panic if the slice does not match the size of this secret.
|
||||
#[inline(always)]
|
||||
pub fn from_bytes_then_nuke(b: &mut [u8]) -> Self {
|
||||
/// Copy bytes into secret, then delete the previous value, will panic if the slice does not match the size of this secret.
|
||||
pub fn from_bytes_then_delete(b: &mut [u8]) -> Self {
|
||||
let ret = Self(b.try_into().unwrap());
|
||||
b.fill(0);
|
||||
ret
|
||||
}
|
||||
/// Moves bytes into secret, will panic if the slice does not match the size of this secret.
|
||||
/// This is unsafe because it will not destroy the contents of its input.
|
||||
/// # Safety
|
||||
/// Make sure the contents of the input are securely deleted.
|
||||
#[inline(always)]
|
||||
pub unsafe fn from_bytes(b: &[u8]) -> Self {
|
||||
Self(b.try_into().unwrap())
|
||||
@@ -92,7 +87,6 @@ impl<const L: usize> Secret<L> {
|
||||
}
|
||||
|
||||
impl<const L: usize> Drop for Secret<L> {
|
||||
#[inline(always)]
|
||||
fn drop(&mut self) {
|
||||
self.0.fill(0);
|
||||
}
|
||||
|
||||
+5
-3
@@ -7,12 +7,14 @@
|
||||
*/
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum OpenError {
|
||||
pub enum OpenError<IoError> {
|
||||
/// An invalid parameter was supplied to the function.
|
||||
InvalidPublicKey,
|
||||
|
||||
/// Local identity blob is too large to send, even with fragmentation.
|
||||
DataTooLarge,
|
||||
|
||||
RatchetIoError(IoError),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
@@ -59,7 +61,7 @@ pub enum FaultType {
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum ReceiveError {
|
||||
pub enum ReceiveError<IoError> {
|
||||
/// A type of fault that can occur because a remote peer sent us a bad packet.
|
||||
/// Such packets will be ignored by ZSSP but a user of ZSSP might want to log
|
||||
/// them for debugging or tracing.
|
||||
@@ -104,5 +106,5 @@ pub enum ReceiveError {
|
||||
|
||||
/// One of the ratchet saving or lookup functions returned an error, so the packet had to be
|
||||
/// dropped.
|
||||
RatchetIoError,
|
||||
RatchetIoError(IoError),
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ impl<Application: ApplicationLayer> UnassociatedHandshakeCache<Application> {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
false
|
||||
}
|
||||
pub(crate) fn service(&self, current_time: i64) {
|
||||
// Only check for expiration if we have a pending packet.
|
||||
|
||||
+1
-1
@@ -94,7 +94,7 @@ impl<T, P: Ord> IndexedBinaryHeap<T, P> {
|
||||
(idx.0 < self.map.len() && self.map[idx.0].1 == idx.1).then(|| self.map[idx.0].0)
|
||||
}
|
||||
pub fn pop(&mut self) -> Option<(T, P)> {
|
||||
(self.data.len() > 0).then(|| self.remove_idx(0))
|
||||
(!self.data.is_empty()).then(|| self.remove_idx(0))
|
||||
}
|
||||
/// Add an item to the queue and get back a generational index which allows for quick updating
|
||||
/// of this item and its priority.
|
||||
|
||||
+5
-3
@@ -14,11 +14,13 @@ mod handshake_cache;
|
||||
mod indexed_heap;
|
||||
mod log_event;
|
||||
mod proto;
|
||||
mod ratchet_state;
|
||||
mod symmetric_state;
|
||||
mod zssp;
|
||||
|
||||
pub mod error;
|
||||
pub use crate::applicationlayer::{ApplicationLayer, RestoreAction, SaveAction};
|
||||
pub use crate::applicationlayer::ApplicationLayer;
|
||||
pub use crate::log_event::LogEvent;
|
||||
pub use crate::proto::{MAX_IDENTITY_BLOB_SIZE, MIN_PACKET_SIZE, MIN_TRANSPORT_MTU, RATCHET_FINGERPRINT_SIZE, RATCHET_KEY_SIZE};
|
||||
pub use crate::zssp::{AcceptSessionAction, Context, ContextInner, IncomingSessionAction, ReceiveResult, Session, SessionEvent};
|
||||
pub use crate::proto::{MAX_IDENTITY_BLOB_SIZE, MIN_PACKET_SIZE, MIN_TRANSPORT_MTU, RATCHET_SIZE};
|
||||
pub use crate::ratchet_state::RatchetState;
|
||||
pub use crate::zssp::{Context, ContextInner, IncomingSessionAction, ReceiveResult, Session, SessionEvent};
|
||||
|
||||
+17
-14
@@ -21,9 +21,7 @@ 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;
|
||||
|
||||
pub const RATCHET_KEY_SIZE: usize = 32;
|
||||
|
||||
pub const RATCHET_FINGERPRINT_SIZE: usize = 32;
|
||||
pub const RATCHET_SIZE: usize = 32;
|
||||
|
||||
/// The application has the ability to attach a data payload to Alice's handshake.
|
||||
/// It will be the first payload Bob receives from Alice.
|
||||
@@ -48,7 +46,7 @@ pub(crate) const PACKET_TYPE_NOISE_XK_PATTERN_1: u8 = 0;
|
||||
pub(crate) const PACKET_TYPE_NOISE_XK_PATTERN_2: u8 = 1;
|
||||
pub(crate) const PACKET_TYPE_NOISE_XK_PATTERN_3: u8 = 2;
|
||||
pub(crate) const PACKET_TYPE_KEY_CONFIRM: u8 = 3;
|
||||
pub(crate) const PACKET_TYPE_KEY_DELETE: u8 = 4;
|
||||
pub(crate) const PACKET_TYPE_ACK: u8 = 4;
|
||||
pub(crate) const PACKET_TYPE_NOISE_KK_PATTERN_1: u8 = 5;
|
||||
pub(crate) const PACKET_TYPE_NOISE_KK_PATTERN_2: u8 = 6;
|
||||
pub(crate) const PACKET_TYPE_SESSION_REJECTED: u8 = 7;
|
||||
@@ -126,7 +124,7 @@ pub(crate) const MAX_UNASSOCIATED_HANDSHAKE_STATES: usize = 32;
|
||||
|
||||
/// The maximum size a packet that is not associated to a session may be.
|
||||
/// Excludes the size of headers for fragmentation.
|
||||
pub(crate) const MAX_UNASSOCIATED_PACKET_SIZE: usize = NoiseXKPattern1::SIZE - HEADER_SIZE;
|
||||
pub(crate) const MAX_UNASSOCIATED_PACKET_SIZE: usize = NoiseXKPattern1::MAX_SIZE - HEADER_SIZE;
|
||||
|
||||
/*
|
||||
XKhfs+psk2:
|
||||
@@ -168,10 +166,11 @@ pub(crate) struct NoiseXKPattern1 {
|
||||
pub noise_e1: [u8; KYBER_PUBLICKEYBYTES],
|
||||
/// -- end encrypted section
|
||||
pub e1_gcm_tag: [u8; AES_GCM_TAG_SIZE],
|
||||
/// -- start AES-GCM(k_es) encrypted section
|
||||
pub ratchet_fingerprint: [u8; RATCHET_FINGERPRINT_SIZE],
|
||||
/// -- end encrypted section
|
||||
pub p_gcm_tag: [u8; AES_GCM_TAG_SIZE],
|
||||
pub payload: [u8; RATCHET_SIZE + RATCHET_SIZE + AES_GCM_TAG_SIZE + ChallengeResponse::SIZE],
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub(crate) struct ChallengeResponse {
|
||||
pub challenge_counter: [u8; CHALLENGE_COUNTER_SIZE],
|
||||
pub challenge_mac: [u8; CHALLENGE_MAC_SIZE],
|
||||
pub challenge_pow: [u8; CHALLENGE_POW_SIZE],
|
||||
@@ -183,9 +182,12 @@ impl NoiseXKPattern1 {
|
||||
pub const E1_ENC_START: usize = Self::PROLOGUE_END + P384_PUBLIC_KEY_SIZE;
|
||||
pub const E1_AUTH_START: usize = Self::E1_ENC_START + KYBER_PUBLICKEYBYTES;
|
||||
pub const P_ENC_START: usize = Self::E1_AUTH_START + AES_GCM_TAG_SIZE;
|
||||
pub const P_AUTH_START: usize = Self::P_ENC_START + RATCHET_FINGERPRINT_SIZE;
|
||||
pub const P_AUTH_END: usize = Self::P_AUTH_START + AES_GCM_TAG_SIZE;
|
||||
pub const SIZE: usize = Self::P_AUTH_END + CHALLENGE_COUNTER_SIZE + CHALLENGE_MAC_SIZE + CHALLENGE_POW_SIZE;
|
||||
|
||||
pub const MIN_SIZE: usize = Self::P_ENC_START + AES_GCM_TAG_SIZE + ChallengeResponse::SIZE;
|
||||
pub const MAX_SIZE: usize = Self::MIN_SIZE + RATCHET_SIZE + RATCHET_SIZE;
|
||||
}
|
||||
impl ChallengeResponse {
|
||||
pub const SIZE: usize = CHALLENGE_COUNTER_SIZE + CHALLENGE_MAC_SIZE + CHALLENGE_POW_SIZE;
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
@@ -269,16 +271,17 @@ impl ProtocolFlatBuffer for NoiseXKPattern1 {}
|
||||
impl ProtocolFlatBuffer for NoiseXKPattern2 {}
|
||||
impl ProtocolFlatBuffer for NoiseKKPattern1or2 {}
|
||||
impl ProtocolFlatBuffer for BobDOSChallenge {}
|
||||
impl ProtocolFlatBuffer for ChallengeResponse {}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn byte_array_as_proto_buffer<B: ProtocolFlatBuffer>(b: &[u8]) -> &B {
|
||||
assert_eq!(b.len(), size_of::<B>());
|
||||
assert!(b.len() >= size_of::<B>());
|
||||
unsafe { &*b.as_ptr().cast() }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn byte_array_as_proto_buffer_mut<B: ProtocolFlatBuffer>(b: &mut [u8]) -> &mut B {
|
||||
assert_eq!(b.len(), size_of::<B>());
|
||||
assert!(b.len() >= size_of::<B>());
|
||||
unsafe { &mut *b.as_mut_ptr().cast() }
|
||||
}
|
||||
/// Trick rust into letting us use a hasher that returns more than 64 bits.
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/* 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 std::num::NonZeroU64;
|
||||
|
||||
use crate::crypto::secret::Secret;
|
||||
use crate::RATCHET_SIZE;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub enum RatchetState {
|
||||
Null,
|
||||
Empty,
|
||||
NonEmpty(NonEmptyRatchetState),
|
||||
}
|
||||
use RatchetState::*;
|
||||
impl RatchetState {
|
||||
#[inline]
|
||||
pub fn new_nonempty(key: Secret<RATCHET_SIZE>, fingerprint: Secret<RATCHET_SIZE>, chain_len: NonZeroU64) -> Self {
|
||||
NonEmpty(NonEmptyRatchetState { key, fingerprint, chain_len })
|
||||
}
|
||||
#[inline]
|
||||
pub fn new_initial_states() -> [RatchetState; 2] {
|
||||
[RatchetState::Empty, RatchetState::Null]
|
||||
}
|
||||
#[inline]
|
||||
pub fn is_null(&self) -> bool {
|
||||
matches!(self, Null)
|
||||
}
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
matches!(self, Empty)
|
||||
}
|
||||
#[inline]
|
||||
pub fn nonempty(&self) -> Option<&NonEmptyRatchetState> {
|
||||
match self {
|
||||
NonEmpty(rs) => Some(rs),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn chain_len(&self) -> u64 {
|
||||
self.nonempty().map_or(0, |rs| rs.chain_len.get())
|
||||
}
|
||||
#[inline]
|
||||
pub fn fingerprint(&self) -> Option<&[u8; RATCHET_SIZE]> {
|
||||
self.nonempty().map(|rs| rs.fingerprint.as_ref())
|
||||
}
|
||||
#[inline]
|
||||
pub fn key(&self) -> Option<&[u8; RATCHET_SIZE]> {
|
||||
const ZERO_KEY: [u8; RATCHET_SIZE] = [0u8; RATCHET_SIZE];
|
||||
match self {
|
||||
Null => None,
|
||||
Empty => Some(&ZERO_KEY),
|
||||
NonEmpty(rs) => Some(rs.key.as_ref()),
|
||||
}
|
||||
}
|
||||
}
|
||||
/// A ratchet key and fingerprint,
|
||||
/// along with the length of the ratchet chain the keys were derived from.
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct NonEmptyRatchetState {
|
||||
pub key: Secret<RATCHET_SIZE>,
|
||||
pub fingerprint: Secret<RATCHET_SIZE>,
|
||||
pub chain_len: NonZeroU64,
|
||||
}
|
||||
+19
-9
@@ -42,7 +42,7 @@ impl SymmetricState {
|
||||
self.token_counter += 1;
|
||||
|
||||
self.chaining_key.overwrite(&next_ck);
|
||||
Secret::from_bytes_then_nuke(&mut temp_k[..AES_256_KEY_SIZE])
|
||||
Secret::from_bytes_then_delete(&mut temp_k[..AES_256_KEY_SIZE])
|
||||
}
|
||||
/// Corresponds to Noise `MixKeyAndHash`.
|
||||
pub(crate) fn mix_key_and_hash(&mut self, hm: &mut impl HmacSha512, input_key_material: &[u8]) -> [u8; NOISE_HASHLEN] {
|
||||
@@ -56,12 +56,17 @@ impl SymmetricState {
|
||||
temp_h
|
||||
}
|
||||
/// Corresponds to Noise `MixKeyAndHash` followed by `InitializeKey`.
|
||||
pub(crate) fn mix_key_and_hash_initialize_key(&mut self, hm: &mut impl HmacSha512, input_key_material: &[u8]) -> ([u8; NOISE_HASHLEN], Secret<AES_256_KEY_SIZE>) {
|
||||
pub(crate) fn mix_key_and_hash_initialize_key(
|
||||
&mut self,
|
||||
hm: &mut impl HmacSha512,
|
||||
input_key_material: &[u8],
|
||||
) -> ([u8; NOISE_HASHLEN], Secret<AES_256_KEY_SIZE>) {
|
||||
let mut next_ck = Secret::new();
|
||||
let mut temp_h = [0u8; NOISE_HASHLEN];
|
||||
let mut temp_k = [0u8; NOISE_HASHLEN];
|
||||
|
||||
self.kbkdf(hm,
|
||||
self.kbkdf(
|
||||
hm,
|
||||
input_key_material,
|
||||
self.label(),
|
||||
3,
|
||||
@@ -72,20 +77,25 @@ impl SymmetricState {
|
||||
self.token_counter += 1;
|
||||
|
||||
self.chaining_key.overwrite(&next_ck);
|
||||
(temp_h, Secret::from_bytes_then_nuke(&mut temp_k[..AES_256_KEY_SIZE]))
|
||||
(temp_h, Secret::from_bytes_then_delete(&mut temp_k[..AES_256_KEY_SIZE]))
|
||||
}
|
||||
/// Get an additional symmetric key (ASK) that is a collision resistant hash of the transcript,
|
||||
/// is forward secrect and is cryptographically independent from all other produced keys.
|
||||
/// Based on Noise's unstable ASK mechanism, using KBKDF instead of HKDF.
|
||||
/// https://github.com/noiseprotocol/noise_wiki/wiki/Additional-Symmetric-Keys.
|
||||
#[inline(always)]
|
||||
pub(crate) fn get_ask2(&self, hm: &mut impl HmacSha512, label: u8, noise_h: &[u8; NOISE_HASHLEN]) -> (Secret<AES_256_KEY_SIZE>, Secret<AES_256_KEY_SIZE>) {
|
||||
pub(crate) fn get_ask2(
|
||||
&self,
|
||||
hm: &mut impl HmacSha512,
|
||||
label: u8,
|
||||
noise_h: &[u8; NOISE_HASHLEN],
|
||||
) -> (Secret<AES_256_KEY_SIZE>, Secret<AES_256_KEY_SIZE>) {
|
||||
let mut temp_k1 = [0u8; NOISE_HASHLEN];
|
||||
let mut temp_k2 = [0u8; NOISE_HASHLEN];
|
||||
self.kbkdf(hm, noise_h, [b'A', b'S', b'K', label], 2, &mut temp_k1, Some(&mut temp_k2), None);
|
||||
(
|
||||
Secret::from_bytes_then_nuke(&mut temp_k1[..AES_256_KEY_SIZE]),
|
||||
Secret::from_bytes_then_nuke(&mut temp_k2[..AES_256_KEY_SIZE]),
|
||||
Secret::from_bytes_then_delete(&mut temp_k1[..AES_256_KEY_SIZE]),
|
||||
Secret::from_bytes_then_delete(&mut temp_k2[..AES_256_KEY_SIZE]),
|
||||
)
|
||||
}
|
||||
/// Corresponds to Noise `Split`.
|
||||
@@ -97,8 +107,8 @@ impl SymmetricState {
|
||||
// Normally KBKDF would not truncate to derive the correct length of AES keys,
|
||||
// but Noise specifies that the AES keys be truncated from NOISE_HASHLEN to AES_256_KEY_SIZE.
|
||||
(
|
||||
Secret::from_bytes_then_nuke(&mut temp_k1[..AES_256_KEY_SIZE]),
|
||||
Secret::from_bytes_then_nuke(&mut temp_k2[..AES_256_KEY_SIZE]),
|
||||
Secret::from_bytes_then_delete(&mut temp_k1[..AES_256_KEY_SIZE]),
|
||||
Secret::from_bytes_then_delete(&mut temp_k2[..AES_256_KEY_SIZE]),
|
||||
)
|
||||
}
|
||||
#[inline(always)]
|
||||
|
||||
+414
-393
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user