Merge branch 'main' of github.com:zerotier/zssp

This commit is contained in:
Monica Moniot
2023-08-23 08:19:11 -07:00
36 changed files with 5323 additions and 3781 deletions
+3 -14
View File
@@ -1,15 +1,4 @@
/target
/**/target
/**/Cargo.lock
.DS_*
.Icon*
._*
*.o
*.so
*.dylib
*.dSYM
*.a
/.idea
/.nova
*.secret
perf*.data
perf*.old
*.svg
Generated
+315
View File
@@ -0,0 +1,315 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "arrayvec"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
dependencies = [
"zeroize",
]
[[package]]
name = "base16ct"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "cc"
version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01"
dependencies = [
"libc",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "const-oid"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747"
[[package]]
name = "cpufeatures"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1"
dependencies = [
"libc",
]
[[package]]
name = "crypto-bigint"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15"
dependencies = [
"generic-array",
"rand_core",
"subtle",
"zeroize",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "der"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c"
dependencies = [
"const-oid",
"zeroize",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
"subtle",
]
[[package]]
name = "elliptic-curve"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b"
dependencies = [
"base16ct",
"crypto-bigint",
"digest",
"ff",
"generic-array",
"group",
"hkdf",
"rand_core",
"sec1",
"subtle",
"zeroize",
]
[[package]]
name = "ff"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449"
dependencies = [
"rand_core",
"subtle",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
"zeroize",
]
[[package]]
name = "getrandom"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "group"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
dependencies = [
"ff",
"rand_core",
"subtle",
]
[[package]]
name = "hkdf"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437"
dependencies = [
"hmac",
]
[[package]]
name = "hmac"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
"digest",
]
[[package]]
name = "libc"
version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
[[package]]
name = "openssl-sys"
version = "0.9.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "866b5f16f90776b9bb8dc1e1802ac6f0513de3a7a7465867bfbc563dc737faac"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "p384"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209"
dependencies = [
"elliptic-curve",
"primeorder",
]
[[package]]
name = "pkg-config"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
[[package]]
name = "pqc_kyber"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5dd33c0b42d244b01ab4f6cabaeb03c3b875017780fb3903b53b9c91fb6663"
dependencies = [
"rand_core",
]
[[package]]
name = "primeorder"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c2fcef82c0ec6eefcc179b978446c399b3cdf73c392c35604e399eee6df1ee3"
dependencies = [
"elliptic-curve",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "sec1"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
dependencies = [
"base16ct",
"der",
"generic-array",
"subtle",
"zeroize",
]
[[package]]
name = "sha2"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "subtle"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
[[package]]
name = "typenum"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "zeroize"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9"
[[package]]
name = "zssp"
version = "0.0.3"
dependencies = [
"arrayvec",
"hmac",
"openssl-sys",
"p384",
"pqc_kyber",
"rand_core",
"sha2",
"zeroize",
]
+19 -3
View File
@@ -10,7 +10,23 @@ name = "zssp"
path = "src/lib.rs"
doc = true
[profile.bench]
debug = true
[dependencies]
pqc_kyber = { version = "0.6.0", default-features = false, features = ["kyber1024", "std"] }
rand_core = "0.6.4"
hex-literal = "0.4.1"
rand_core = { version = "0.6.4" }
zeroize = { version = "1.6.0" }
arrayvec = { version = "0.7.4", default-features = false, features = ["std", "zeroize"] }
pqc_kyber = { version = "0.7.0", default-features = false, features = ["kyber1024", "std"], optional = true }
p384 = { version = "0.13.0", default-features = false, features = ["ecdh"], optional = true }
sha2 = { version = "0.10.7", default-features = false, optional = true }
hmac = { version = "0.12.1", default-features = false, optional = true }
openssl-sys = { version = "0.9.91", default-features = false, optional = true }
[features]
default = ["debug", "default-crypto"]
default-crypto = ["p384", "sha2", "pqc_kyber", "openssl-sys", "rand_core/getrandom"]
sha2 = ["dep:sha2", "dep:hmac"]
logging = []
debug = ["logging"]
+1 -1
View File
@@ -49,11 +49,11 @@ Further information can be found in the ZSSP whitepaper (pending official releas
* **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.
* **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.
* **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.
+406
View File
@@ -0,0 +1,406 @@
use std::collections::HashMap;
use std::iter::ExactSizeIterator;
use std::str::FromStr;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{mpsc, Arc, Mutex};
use std::thread;
use std::time::{Duration, Instant};
use rand_core::OsRng;
use rand_core::RngCore;
use zssp::application::{
AcceptAction, ApplicationLayer, CryptoLayer, IncomingSessionAction, RatchetState, RatchetStates, RatchetUpdate,
Settings, RATCHET_SIZE,
};
use zssp::crypto::P384KeyPair;
use zssp::crypto_impl::*;
use zssp::result::ReceiveError;
use zssp::Session;
const TEST_MTU: usize = 1500;
struct TestApplication {
time: Instant,
name: &'static str,
ratchets: Mutex<Ratchets>,
}
struct Ratchets {
rf_map: HashMap<[u8; RATCHET_SIZE], RatchetState>,
peer_map: HashMap<u128, RatchetStates>,
}
impl Ratchets {
fn new() -> Self {
Self { rf_map: HashMap::new(), peer_map: HashMap::new() }
}
}
#[allow(unused)]
impl CryptoLayer for TestApplication {
const SETTINGS: Settings = Settings {
initial_offer_timeout: Settings::INITIAL_OFFER_TIMEOUT_MS,
rekey_timeout: 60 * 1000,
rekey_after_time: 3000,
rekey_time_max_jitter: 1000,
rekey_after_key_uses: Settings::REKEY_AFTER_KEY_USES,
resend_time: 250,
fragment_assembly_timeout: Settings::FRAGMENT_ASSEMBLY_TIMEOUT_MS,
};
type Rng = OsRng;
type PrpEnc = OpenSSLAes256Enc;
type PrpDec = OpenSSLAes256Dec;
type Aead = OpenSSLAesGcm;
type AeadPool = OpenSSLAesGcmPool;
type Hash = CrateSha512;
type Hmac = CrateHmacSha512;
type PublicKey = CrateP384PublicKey;
type KeyPair = CrateP384KeyPair;
type Kem = CrateKyber1024PrivateKey;
type SessionData = u128;
type IncomingPacketBuffer = Vec<u8>;
}
#[allow(unused)]
impl ApplicationLayer for &TestApplication {
type Crypto = TestApplication;
fn incoming_session(&mut self) -> IncomingSessionAction {
IncomingSessionAction::Challenge
}
fn hello_requires_recognized_ratchet(&mut self) -> bool {
false
}
fn initiator_disallows_downgrade(&mut self, session: &Arc<Session<TestApplication>>) -> bool {
true
}
fn check_accept_session(
&mut self,
remote_static_key: &CrateP384PublicKey,
identity: &[u8],
) -> AcceptAction<TestApplication> {
AcceptAction {
session_data: Some(1),
responder_disallows_downgrade: true,
responder_silently_rejects: false,
}
}
fn restore_by_fingerprint(&mut self, ratchet_fingerprint: &[u8; RATCHET_SIZE]) -> Result<Option<RatchetState>, ()> {
let ratchets = self.ratchets.lock().unwrap();
Ok(ratchets.rf_map.get(ratchet_fingerprint).cloned())
}
fn restore_by_identity(
&mut self,
remote_static_key: &CrateP384PublicKey,
session_data: &u128,
) -> Result<Option<RatchetStates>, ()> {
let ratchets = self.ratchets.lock().unwrap();
Ok(ratchets.peer_map.get(session_data).cloned())
}
fn save_ratchet_state(
&mut self,
remote_static_key: &CrateP384PublicKey,
session_data: &u128,
update_data: RatchetUpdate<'_>,
) -> Result<(), ()> {
let mut ratchets = self.ratchets.lock().unwrap();
ratchets.peer_map.insert(*session_data, update_data.to_states());
if let Some(rf) = update_data.added_fingerprint() {
ratchets.rf_map.insert(*rf, update_data.state1.clone());
println!("[{}] new ratchet #{}", self.name, update_data.state1.chain_len);
}
if let Some(rf) = update_data.deleted_fingerprint1() {
ratchets.rf_map.remove(rf);
}
if let Some(rf) = update_data.deleted_fingerprint2() {
ratchets.rf_map.remove(rf);
}
Ok(())
}
fn time(&mut self) -> i64 {
self.time.elapsed().as_millis() as i64
}
fn event_log(&mut self, event: zssp::LogEvent<TestApplication>) {
println!(">[{}] {:?}", self.name, event);
}
}
#[allow(unused)]
fn alice_main(
run: &AtomicBool,
packet_success_rate: u32,
alice_app: &TestApplication,
alice_out: mpsc::SyncSender<Vec<u8>>,
alice_in: mpsc::Receiver<Vec<u8>>,
recursive_out: mpsc::SyncSender<Vec<u8>>,
alice_keypair: CrateP384KeyPair,
bob_pubkey: CrateP384PublicKey,
) {
let startup_time = std::time::Instant::now();
let context = zssp::Context::<TestApplication>::new(alice_keypair, OsRng);
let mut next_service = startup_time.elapsed().as_millis() as i64 + 500;
let test_data = [1u8; TEST_MTU * 10];
let mut up = false;
let mut alice_session = None;
while run.load(Ordering::Relaxed) {
if alice_session.is_none() {
up = false;
alice_session = Some(
context
.open(
alice_app,
|b| alice_out.send(b.to_vec()).is_ok(),
TEST_MTU,
bob_pubkey.clone(),
0,
&[],
)
.unwrap(),
);
println!("[alice] opening session");
}
let current_time = startup_time.elapsed().as_millis() as i64;
loop {
let pkt = alice_in.try_recv();
if let Ok(pkt) = pkt {
if OsRng.next_u32() <= packet_success_rate {
use zssp::result::ReceiveOk::*;
use zssp::result::SessionEvent::*;
let mut output_data = Vec::new();
match context.receive(
alice_app,
|b| alice_out.send(b.to_vec()).is_ok(),
TEST_MTU,
|_| Some((|b: &mut [u8]| alice_out.send(b.to_vec()).is_ok(), TEST_MTU)),
&0,
pkt,
&mut output_data,
) {
Ok(Unassociated) => {
//println!("[alice] ok");
}
Ok(Session(_, event)) => match event {
Established => {
up = true;
}
Data => {
assert!(!output_data.is_empty());
//println!("[alice] received {}", data.len());
}
Control => (),
_ => panic!(),
},
Err(e) => {
println!("[alice] ERROR {:?}", e);
if let ReceiveError::ByzantineFault { unnatural, .. } = e {
assert!(!unnatural)
}
}
}
//} else if OsRng.next_u32() | 1 > 0 {
// let _ = recursive_out.send(pkt);
}
} else {
break;
}
}
if up {
context
.send(
alice_session.as_ref().unwrap(),
|b| alice_out.send(b.to_vec()).is_ok(),
&mut [0u8; TEST_MTU],
&test_data[..1400 + ((OsRng.next_u64() as usize) % (test_data.len() - 1400))],
)
.unwrap();
} else {
thread::sleep(Duration::from_millis(10));
}
// TODO: we need to more comprehensively test if re-opening the session works
if OsRng.next_u32() <= ((u32::MAX as f64) * 0.0000005) as u32 {
alice_session = None;
}
if current_time >= next_service {
next_service = current_time
+ context.service(alice_app, |_| {
Some((|b: &mut [u8]| alice_out.send(b.to_vec()).is_ok(), TEST_MTU))
});
}
}
}
#[allow(unused)]
fn bob_main(
run: &AtomicBool,
packet_success_rate: u32,
bob_app: &TestApplication,
bob_out: mpsc::SyncSender<Vec<u8>>,
bob_in: mpsc::Receiver<Vec<u8>>,
recursive_out: mpsc::SyncSender<Vec<u8>>,
bob_keypair: CrateP384KeyPair,
) {
let startup_time = std::time::Instant::now();
let context = zssp::Context::<TestApplication>::new(bob_keypair, OsRng);
let mut last_speed_metric = startup_time.elapsed().as_millis() as i64;
let mut next_service = last_speed_metric + 500;
let mut transferred = 0u64;
let mut bob_session = None;
while run.load(Ordering::Relaxed) {
let pkt = bob_in.recv_timeout(Duration::from_millis(100));
let current_time = startup_time.elapsed().as_millis() as i64;
if let Ok(pkt) = pkt {
if OsRng.next_u32() <= packet_success_rate {
use zssp::result::ReceiveOk::*;
use zssp::result::SessionEvent::*;
let mut output_data = Vec::new();
match context.receive(
bob_app,
|b| bob_out.send(b.to_vec()).is_ok(),
TEST_MTU,
|_| Some((|b: &mut [u8]| bob_out.send(b.to_vec()).is_ok(), TEST_MTU)),
&0,
pkt,
&mut output_data,
) {
Ok(Unassociated) => {}
Ok(Session(s, event)) => match event {
NewSession | NewDowngradedSession => {
println!("[bob] new session, took {}s", current_time as f32 / 1000.0);
let _ = bob_session.replace(s);
}
Data => {
assert!(!output_data.is_empty());
//println!("[bob] received {}", output_data.len());
transferred += output_data.len() as u64 * 2; // *2 because we are also sending this many bytes back
context
.send(
&s,
|b| bob_out.send(b.to_vec()).is_ok(),
&mut [0u8; TEST_MTU],
&output_data,
)
.unwrap();
}
Control => (),
_ => panic!(),
},
Err(e) => {
println!("[bob] ERROR {:?}", e);
if let ReceiveError::ByzantineFault { unnatural, .. } = e {
assert!(!unnatural)
}
}
}
//} else if OsRng.next_u32() | 1 > 0 {
// let _ = recursive_out.try_send(pkt);
}
}
let speed_metric_elapsed = current_time - last_speed_metric;
if speed_metric_elapsed >= 10000 {
last_speed_metric = current_time;
println!(
"[bob] throughput: {} MiB/sec (combined input and output)",
((transferred as f64) / 1048576.0) / ((speed_metric_elapsed as f64) / 1000.0)
);
transferred = 0;
}
if current_time >= next_service {
next_service = current_time
+ context.service(bob_app, |_| {
Some((|b: &mut [u8]| bob_out.send(b.to_vec()).is_ok(), TEST_MTU))
});
}
}
}
fn core(time: u64, packet_success_rate: u32) {
let run = &AtomicBool::new(true);
let alice_keypair = CrateP384KeyPair::generate(&mut OsRng);
let alice_app = TestApplication {
time: Instant::now(),
name: "alice",
ratchets: Mutex::new(Ratchets::new()),
};
let bob_keypair = CrateP384KeyPair::generate(&mut OsRng);
let bob_pubkey = bob_keypair.public_key();
let bob_app = TestApplication {
time: Instant::now(),
name: "bob",
ratchets: Mutex::new(Ratchets::new()),
};
let (alice_out, bob_in) = mpsc::sync_channel::<Vec<u8>>(256);
let (bob_out, alice_in) = mpsc::sync_channel::<Vec<u8>>(256);
thread::scope(|ts| {
{
let alice_out = alice_out.clone();
let bob_out = bob_out.clone();
ts.spawn(move || {
alice_main(
run,
packet_success_rate,
&alice_app,
alice_out,
alice_in,
bob_out,
alice_keypair,
bob_pubkey,
)
});
}
ts.spawn(move || {
bob_main(
run,
packet_success_rate,
&bob_app,
bob_out,
bob_in,
alice_out,
bob_keypair,
)
});
thread::sleep(Duration::from_secs(time));
run.store(false, Ordering::SeqCst);
println!("finished");
});
}
fn main() {
let args = std::env::args();
let packet_success_rate = if args.len() <= 1 {
let default_success_rate = 1.0;
((u32::MAX as f64) * default_success_rate) as u32
} else {
((u32::MAX as f64) * f64::from_str(args.last().unwrap().as_str()).unwrap()) as u32
};
core(60 * 60, packet_success_rate)
}
#[test]
fn test_main() {
core(2, u32::MAX / 2)
}
+324
View File
@@ -0,0 +1,324 @@
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{mpsc, Arc, Mutex};
use std::thread;
use std::time::{Duration, Instant};
use arrayvec::ArrayVec;
use rand_core::OsRng;
use rand_core::RngCore;
use zssp::application::{
AcceptAction, ApplicationLayer, IncomingSessionAction, RatchetState, RatchetStates, RatchetUpdate, RATCHET_SIZE,
};
use zssp::crypto::P384KeyPair;
use zssp::crypto_impl::*;
use zssp::result::ReceiveError;
use zssp::Session;
const TEST_MTU: usize = 1500;
struct TestApplication {
time: Instant,
}
/// We have to pool allocations or else variations in the speed of the memory allocator will bias
/// our performance stats.
struct PooledVec(Vec<u8>);
static POOL: Mutex<Vec<Vec<u8>>> = Mutex::new(Vec::new());
fn alloc(b: &[u8]) -> PooledVec {
let mut p = POOL.lock().unwrap();
let mut v = p.pop().unwrap_or_default();
v.extend(b);
PooledVec(v)
}
impl Drop for PooledVec {
fn drop(&mut self) {
let mut p = POOL.lock().unwrap();
let mut v = Vec::new();
std::mem::swap(&mut self.0, &mut v);
v.clear();
p.push(v);
}
}
impl AsMut<[u8]> for PooledVec {
fn as_mut(&mut self) -> &mut [u8] {
self.0.as_mut()
}
}
impl AsRef<[u8]> for PooledVec {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
#[allow(unused)]
impl DefaultCrypto for TestApplication {
type SessionData = ();
type IncomingPacketBuffer = PooledVec;
}
#[allow(unused)]
impl ApplicationLayer for &TestApplication {
type Crypto = TestApplication;
fn incoming_session(&mut self) -> IncomingSessionAction {
IncomingSessionAction::Allow
}
fn hello_requires_recognized_ratchet(&mut self) -> bool {
false
}
fn initiator_disallows_downgrade(&mut self, session: &Arc<Session<TestApplication>>) -> bool {
false
}
fn check_accept_session(
&mut self,
remote_static_key: &CrateP384PublicKey,
identity: &[u8],
) -> AcceptAction<TestApplication> {
AcceptAction {
session_data: Some(()),
responder_disallows_downgrade: true,
responder_silently_rejects: false,
}
}
fn restore_by_fingerprint(&mut self, ratchet_fingerprint: &[u8; RATCHET_SIZE]) -> Result<Option<RatchetState>, ()> {
Ok(None)
}
fn restore_by_identity(
&mut self,
remote_static_key: &CrateP384PublicKey,
session_data: &(),
) -> Result<Option<RatchetStates>, ()> {
Ok(None)
}
fn save_ratchet_state(
&mut self,
remote_static_key: &CrateP384PublicKey,
session_data: &(),
update_data: RatchetUpdate<'_>,
) -> Result<(), ()> {
Ok(())
}
fn time(&mut self) -> i64 {
self.time.elapsed().as_millis() as i64
}
}
#[allow(unused)]
fn alice_main(
run: &AtomicBool,
alice_app: &TestApplication,
alice_out: mpsc::SyncSender<PooledVec>,
alice_in: mpsc::Receiver<PooledVec>,
alice_keypair: CrateP384KeyPair,
bob_pubkey: CrateP384PublicKey,
) {
let startup_time = std::time::Instant::now();
let context = zssp::Context::<TestApplication>::new(alice_keypair, OsRng);
let mut next_service = startup_time.elapsed().as_millis() as i64 + 500;
let test_data = [1u8; TEST_MTU * 10];
let mut up = false;
let mut output_data = ArrayVec::<u8, 15000>::new();
let alice_session = Some(
context
.open(
alice_app,
|b| alice_out.send(alloc(b)).is_ok(),
TEST_MTU,
bob_pubkey.clone(),
(),
&[],
)
.unwrap(),
);
println!("[alice] opening session");
while run.load(Ordering::Relaxed) {
let current_time = startup_time.elapsed().as_millis() as i64;
loop {
let pkt = alice_in.try_recv();
if let Ok(pkt) = pkt {
use zssp::result::ReceiveOk::*;
use zssp::result::SessionEvent::*;
output_data.clear();
match context.receive(
alice_app,
|b| alice_out.send(alloc(b)).is_ok(),
TEST_MTU,
|_| Some((|b: &mut [u8]| alice_out.send(alloc(b)).is_ok(), TEST_MTU)),
&0,
pkt,
&mut output_data,
) {
Ok(Unassociated) => {
//println!("[alice] ok");
}
Ok(Session(_, event)) => match event {
Established => {
up = true;
}
Data => {
assert!(!output_data.is_empty());
//println!("[alice] received {}", data.len());
}
Control => (),
_ => panic!(),
},
Err(e) => {
println!("[alice] ERROR {:?}", e);
if let ReceiveError::ByzantineFault { unnatural, .. } = e {
assert!(!unnatural)
}
}
}
} else {
break;
}
}
if up {
context
.send(
alice_session.as_ref().unwrap(),
|b| alice_out.send(alloc(b)).is_ok(),
&mut [0u8; TEST_MTU],
&test_data[..1400 + ((OsRng.next_u64() as usize) % (test_data.len() - 1400))],
)
.unwrap();
} else {
thread::sleep(Duration::from_millis(10));
}
if current_time >= next_service {
next_service = current_time
+ context.service(alice_app, |_| {
Some((|b: &mut [u8]| alice_out.send(alloc(b)).is_ok(), TEST_MTU))
});
}
}
}
#[allow(unused)]
fn bob_main(
run: &AtomicBool,
bob_app: &TestApplication,
bob_out: mpsc::SyncSender<PooledVec>,
bob_in: mpsc::Receiver<PooledVec>,
bob_keypair: CrateP384KeyPair,
) {
let startup_time = std::time::Instant::now();
let context = zssp::Context::<TestApplication>::new(bob_keypair, OsRng);
let mut last_speed_metric = startup_time.elapsed().as_millis() as i64;
let mut next_service = last_speed_metric + 500;
let mut transferred = 0u64;
let mut output_data = ArrayVec::<u8, 15000>::new();
let mut bob_session = None;
while run.load(Ordering::Relaxed) {
let pkt = bob_in.recv_timeout(Duration::from_millis(100));
let current_time = startup_time.elapsed().as_millis() as i64;
if let Ok(pkt) = pkt {
use zssp::result::ReceiveOk::*;
use zssp::result::SessionEvent::*;
output_data.clear();
match context.receive(
bob_app,
|b| bob_out.send(alloc(b)).is_ok(),
TEST_MTU,
|_| Some((|b: &mut [u8]| bob_out.send(alloc(b)).is_ok(), TEST_MTU)),
&0,
pkt,
&mut output_data,
) {
Ok(Unassociated) => {}
Ok(Session(s, event)) => match event {
NewSession | NewDowngradedSession => {
println!("[bob] new session, took {}s", current_time as f32 / 1000.0);
let _ = bob_session.replace(s);
}
Data => {
assert!(!output_data.is_empty());
//println!("[bob] received {}", output_data.len());
transferred += output_data.len() as u64 * 2; // *2 because we are also sending this many bytes back
context
.send(
&s,
|b| bob_out.send(alloc(b)).is_ok(),
&mut [0u8; TEST_MTU],
&output_data,
)
.unwrap();
}
Control => (),
_ => panic!(),
},
Err(e) => {
println!("[bob] ERROR {:?}", e);
if let ReceiveError::ByzantineFault { unnatural, .. } = e {
assert!(!unnatural)
}
}
}
}
let speed_metric_elapsed = current_time - last_speed_metric;
if speed_metric_elapsed >= 10000 {
last_speed_metric = current_time;
println!(
"[bob] throughput: {} MiB/sec (combined input and output)",
((transferred as f64) / 1048576.0) / ((speed_metric_elapsed as f64) / 1000.0)
);
transferred = 0;
}
if current_time >= next_service {
next_service = current_time
+ context.service(bob_app, |_| {
Some((|b: &mut [u8]| bob_out.send(alloc(b)).is_ok(), TEST_MTU))
});
}
}
}
fn core(time: u64) {
let run = &AtomicBool::new(true);
let alice_keypair = CrateP384KeyPair::generate(&mut OsRng);
let alice_app = TestApplication { time: Instant::now() };
let bob_keypair = CrateP384KeyPair::generate(&mut OsRng);
let bob_pubkey = bob_keypair.public_key();
let bob_app = TestApplication { time: Instant::now() };
let (alice_out, bob_in) = mpsc::sync_channel::<PooledVec>(256);
let (bob_out, alice_in) = mpsc::sync_channel::<PooledVec>(256);
thread::scope(|ts| {
{
let alice_out = alice_out.clone();
ts.spawn(move || alice_main(run, &alice_app, alice_out, alice_in, alice_keypair, bob_pubkey));
}
ts.spawn(move || bob_main(run, &bob_app, bob_out, bob_in, bob_keypair));
thread::sleep(Duration::from_secs(time));
run.store(false, Ordering::SeqCst);
println!("finished");
});
}
fn main() {
core(20)
}
#[test]
fn test_main() {
core(2)
}
+1 -1
View File
@@ -1,4 +1,4 @@
max_width = 150
max_width = 120
edition = "2021"
newline_style = "Unix"
struct_lit_width = 60
+24
View File
@@ -0,0 +1,24 @@
use std::sync::atomic::{AtomicU64, Ordering};
pub struct Window<const L: usize, const MAX: u64>([AtomicU64; L]);
impl<const L: usize, const MAX: u64> Window<L, MAX> {
pub fn new() -> Self {
Self(std::array::from_fn(|_| AtomicU64::new(0)))
}
/// Check the window without mutating state.
pub fn check(&self, counter: u64) -> bool {
let slot = &self.0[(counter as usize) % self.0.len()];
let counter = counter.wrapping_add(1);
let prev_counter = slot.load(Ordering::Relaxed);
prev_counter < counter && counter.wrapping_sub(prev_counter) <= MAX
}
/// Update the window, returning true if the packet is still valid.
/// This should only be called after the packet is authenticated.
pub fn update(&self, counter: u64) -> bool {
let slot = &self.0[(counter as usize) % self.0.len()];
let counter = counter.wrapping_add(1);
let prev_counter = slot.fetch_max(counter, Ordering::Relaxed);
prev_counter < counter && counter.wrapping_sub(prev_counter) <= MAX
}
}
+274
View File
@@ -0,0 +1,274 @@
use rand_core::{CryptoRng, RngCore};
use std::sync::Arc;
use crate::crypto::*;
use crate::zeta::Session;
pub use crate::proto::RATCHET_SIZE;
pub use crate::ratchet_state::*;
/// A container for a vast majority of the dynamic settings within ZSSP, including all time-based settings.
/// If the user wishes to measure time in units other than milliseconds for some reason, then they can
/// create an adjusted version of this struct with those units, and use it instead of the default.
pub struct Settings {
/// Timeout for how long Alice should wait for Bob to confirm that the Noise_XK handshake
/// was completed successfully. The handshake attempt will be assumed as failed and
/// restarted if Bob does not respond by this cut-off.
pub initial_offer_timeout: u64,
/// Timeout for how long ZSSP should wait before expiring and closing a session when it has
/// lingered in certain states for too long, primarily the rekeying states.
/// If a remote peer does not send the correct information to rekey a session before this
/// timeout then the session will close.
pub rekey_timeout: u64,
/// How long until rekeying should occur for each new session key.
pub rekey_after_time: u64,
/// Maximum random jitter to subtract from the rekey after time timer.
/// Must be greater than 0.
/// This prevents rekeying from occurring predictably on the hour, so traffic analysis is harder.
pub rekey_time_max_jitter: u64,
/// How many key uses may occur before the session starts attempting to rekey.
/// The session will forceably close at 2^32 key uses so it is recommended this value be smaller.
pub rekey_after_key_uses: u64,
/// Retry interval for outgoing connection initiation or rekey attempts.
///
/// Retry attempts will be no more often than this, but the delay may end up being
/// slightly more in some cases based on the rate of calls to `service`.
pub resend_time: u64,
/// How long fragments are allowed to linger in the defragmentation buffer before they are dropped.
/// This implementation of a defrag buffer only bounds memory consumption based on this value.
pub fragment_assembly_timeout: u64,
}
impl Settings {
/// Default value for the `initial_offer_timeout`.
/// The default value is 10 seconds in ms.
pub const INITIAL_OFFER_TIMEOUT_MS: u64 = 10 * 1000;
/// Default value for the `rekey_timeout`.
/// The default value is 1 minute in ms.
pub const REKEY_TIMEOUT_MS: u64 = 60 * 1000;
/// Default value for the `rekey_after_time`.
/// The default value is 1 hour in ms.
pub const REKEY_AFTER_TIME_MS: u64 = 60 * 60 * 1000;
/// Default value for the `rekey_time_max_jitter`.
/// The default is 10 minutes in ms.
pub const REKEY_AFTER_TIME_MAX_JITTER_MS: u64 = 10 * 60 * 1000;
/// Default value for the `rekey_after_key_uses`.
/// The default is 2^30.
pub const REKEY_AFTER_KEY_USES: u64 = 1 << 30;
/// Default value for the `resend_time`.
/// The default is 1 second in ms.
pub const RESEND_TIME: u64 = 1000;
/// Default value for the `fragment_assembly_timeout`.
/// The default is 5 seconds in ms.
pub const FRAGMENT_ASSEMBLY_TIMEOUT_MS: u64 = 5 * 1000;
/// Create an instance of Settings with all default values.
/// These defaults are in units of milliseconds, so if these defaults are used, `App::time`
/// must return timestamps in unts of milliseconds as well.
pub const fn new_ms() -> Self {
Self {
initial_offer_timeout: Self::INITIAL_OFFER_TIMEOUT_MS,
rekey_timeout: Self::REKEY_TIMEOUT_MS,
rekey_after_time: Self::REKEY_AFTER_TIME_MS,
rekey_time_max_jitter: Self::REKEY_AFTER_TIME_MAX_JITTER_MS,
rekey_after_key_uses: Self::REKEY_AFTER_KEY_USES,
resend_time: Self::RESEND_TIME,
fragment_assembly_timeout: Self::FRAGMENT_ASSEMBLY_TIMEOUT_MS,
}
}
}
impl Default for Settings {
fn default() -> Self {
Self::new_ms()
}
}
/// 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. Both sides of a ZSSP session **must** have these constants
/// set to the same values. Changing these constants is generally discouraged unless you know
/// what you are doing.
pub trait CryptoLayer: Sized {
/// These are constants that can be redefined from their defaults to change rekey
/// and negotiation timeout behavior. If two sides of a ZSSP session have different constants,
/// the protocol will tend to default to the smaller constants.
const SETTINGS: Settings = Settings::new_ms();
type Rng: CryptoRng + RngCore;
/// The implementation of AES-256 Encryption that ZSSP should use.
///
/// FIPS compliance requires use of a FIPS certified implementation.
type PrpEnc: Aes256Enc;
/// The implementation of AES-256 Decryption that ZSSP should use.
///
/// FIPS compliance requires use of a FIPS certified implementation.
type PrpDec: Aes256Dec;
type Aead: LowThroughputAesGcm;
type AeadPool: HighThroughputAesGcmPool;
/// The implementation of SHA-512 that ZSSP should use.
///
/// FIPS compliance requires use of a FIPS certified implementation.
type Hash: Sha512Hash;
type Hmac: Sha512Hmac;
/// The implementation of P-384 public keys that ZSSP should use.
///
/// FIPS compliance requires a FIPS certified implementation.
type PublicKey: P384PublicKey;
/// The implementation of P-384 private keys that ZSSP should use.
///
/// FIPS compliance requires use of a FIPS certified implementation.
type KeyPair: P384KeyPair<Self::Rng, PublicKey = Self::PublicKey>;
/// The implementation of Kyber1024 that ZSSP should use.
///
/// No implementation of Kyber1024 can be FIPS certified, but this is not required
/// for ZSSP to achieve FIPS compliance.
type Kem: Kyber1024PrivateKey<Self::Rng>;
/// Type for arbitrary opaque object for use by the application that is attached to
/// each session.
type SessionData;
/// Data type for incoming packet buffers.
///
/// 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]>;
}
pub trait ApplicationLayer: Sized {
type Crypto: CryptoLayer;
/// Should return the current time in milliseconds. Does not have to be monotonic, nor synced
/// with remote peers (although both of these properties would help reliability slightly).
/// Used to determine if any current handshakes should be resent or timed-out, or if a session
/// should rekey.
fn time(&mut self) -> i64;
fn incoming_session(&mut self) -> IncomingSessionAction;
/// 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 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.
fn hello_requires_recognized_ratchet(&mut self) -> bool;
/// This function is called if we, as Alice, attempted to open a session with Bob using a
/// 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 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.
///
/// This function must deterministically return either true or false for a given session.
///
/// It is a bad sign that Bob has somehow forgotten Alice's ratchet key, it either means at
/// 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.
fn initiator_disallows_downgrade(&mut self, session: &Arc<Session<Self::Crypto>>) -> bool;
/// Function to accept sessions after final negotiation.
/// The second argument is the identity that the remote peer sent us. The application
/// must verify this identity is associated with the remote peer's static key.
/// To prevent desync, if this function specifies that we should connect, no other open session
/// with the same remote peer must exist. Drop or call expire on any pre-existing sessions
/// before returning.
fn check_accept_session(
&mut self,
remote_static_key: &<Self::Crypto as CryptoLayer>::PublicKey,
identity: &[u8],
) -> AcceptAction<Self::Crypto>;
/// 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 a ratchet state with a matching fingerprint could not be found, this function should
/// return `Ok(None)`.
fn restore_by_fingerprint(&mut self, ratchet_fingerprint: &[u8; RATCHET_SIZE]) -> Result<Option<RatchetState>, ()>;
/// Lookup the specific ratchet states 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.
///
/// If the peer's ratchet states could not be could, this function should return
/// `RatchetState::new_initial_states()`.
///
/// If a one-time-password has been pre-shared with this peer, `RatchetState::new_otp_states(...)`
/// should be pre-saved to the storage backend as if it is a normal ratchet state.
/// This is to ensure it can both be restored and eventually deleted when it is used.
///
/// This function is not responsible for deciding whether or not to connect to this remote peer.
/// Filtering peers should be done by the caller to `Context::open` as well as by the
/// function `ApplicationLayer::check_accept_session`.
fn restore_by_identity(
&mut self,
remote_static_key: &<Self::Crypto as CryptoLayer>::PublicKey,
session_data: &<Self::Crypto as CryptoLayer>::SessionData,
) -> Result<Option<RatchetStates>, ()>;
/// Atomically commit the update specified by `update_data` to storage, or return an error if
/// the update could not be made.
/// The implementor is free to choose how to apply these updates to storage.
///
/// 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 empty.
///
/// This function may also save state to volatile storage, in which case all peers which connect
/// to us will have to allow downgrade across the board.
/// Otherwise, when we restart, we will not be allowed to reconnect.
fn save_ratchet_state(
&mut self,
remote_static_key: &<Self::Crypto as CryptoLayer>::PublicKey,
session_data: &<Self::Crypto as CryptoLayer>::SessionData,
update_data: RatchetUpdate<'_>,
) -> Result<(), ()>;
/// Receives a stream of events that occur during an execution of ZSSP.
/// These are provided for debugging, logging or metrics purposes, and must be used for
/// nothing else. Do not base protocol-level decisions upon the events passed to this function.
#[cfg(feature = "logging")]
#[allow(unused)]
fn event_log(&mut self, event: crate::LogEvent<'_, Self::Crypto>) {}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum IncomingSessionAction {
Allow,
Challenge,
Drop,
}
/// A collection of fields specifying how to complete the key exchange with a specific remote peer,
/// used by Bob, the responder, at the very last stage of the key exchange.
///
/// Corresponds to the *Accept* callback of Transition Algorithm 4.
pub struct AcceptAction<Crypto: CryptoLayer> {
/// The data object to be attached to the session if we successfully connect.
/// If this field is None then we will not connect to this remote peer.
pub session_data: Option<Crypto::SessionData>,
/// Whether or not we will accept a connection with the remote peer when they do not have a
/// ratchet key that we think they should have.
pub responder_disallows_downgrade: bool,
/// Whether or not to send an explicit rejection packet to the remote peer if we do not create
/// a session with them.
///
/// This field will not be used if `session_data` is `Some` and the remote peer passes all other
/// authentication checks.
pub responder_silently_rejects: bool,
}
-196
View File
@@ -1,196 +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 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::rand_core::{CryptoRng, RngCore};
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.
///
/// 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. Both sides of a ZSSP session **must** have these constants
/// set to the same values. Changing these constants is generally discouraged unless you know
/// what you are doing.
pub trait ApplicationLayer: Sized {
/// Retry interval for outgoing connection initiation or rekey attempts.
///
/// Retry attempts will be no more often than this, but the delay may end up being
/// slightly more in some cases depending on where in the cycle the initial attempt
/// falls.
///
/// Default value is 1 second.
const RETRY_INTERVAL_MS: i64 = 1000;
/// Timeout for how long Alice should wait for Bob to confirm that the Noise_XK handshake
/// was completed successfully. The handshake attempt will be assumed as failed and
/// restarted if Bob does not respond by this cut-off.
///
/// Default is 10 seconds.
const INITIAL_OFFER_TIMEOUT_MS: i64 = 10 * 1000;
/// Timeout for how long ZSSP should wait before expiring and closing a session when it has
/// lingered in certain states for too long, primarily the rekeying states.
/// If a remote peer does not send the correct information to rekey a session before this
/// timeout then the session will close.
///
/// Default is 1 minute.
const EXPIRATION_TIMEOUT_MS: i64 = 60 * 1000;
/// Start attempting to rekey after a key has been in use for this many milliseconds.
///
/// Default is 1 hour.
const REKEY_AFTER_TIME_MS: i64 = 1000 * 60 * 60;
/// Maximum random jitter to subtract from the rekey after time timer.
/// Must be greater than 0 and less than u32::MAX.
/// This prevents rekeying from occurring predictably on the hour, so traffic analysis is harder.
///
/// Default is 10 minutes.
const REKEY_AFTER_TIME_MAX_JITTER_MS: i64 = 1000 * 60 * 10;
/// 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 = 1073741824;
/// Hard expiration of a key after this many uses.
///
/// Attempting to encrypt more than this many messages with a key will cause a hard error
/// and prevent all encryption.
/// This should basically never occur in practice because of rekeying.
///
/// Default value is 2^32 - 1, one less than NIST's recommended limit.
/// https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
const EXPIRE_AFTER_USES: u64 = 4294967295;
/// Determines how computationally difficult the proof of work is when Bob challenges Alice.
/// It is extremely computationally expensive on Bob to process Alice's initiation packet. So
/// Bob has the option to challenge Alice to prove ownership of address and to prove work before
/// they attempt process Alice's initiation packet.
/// The amount of computational work Alice has to prove increases exponentially with this value.
///
/// This value must be between 0 and 32 (inclusive).
///
/// Default is 13, which, on a modern processor, ensures Alice will have to do about as much
/// computational work as Bob will when they process Alice's initiation packet.
const PROOF_OF_WORK_BIT_DIFFICULTY: u32 = 13;
type Rng: CryptoRng + RngCore;
type PrpEnc: AesEnc;
type PrpDec: AesDec;
type AeadEnc: AesGcmEnc;
type AeadDec: AesGcmDec;
type Hash: Sha512;
type HmacHash: HmacSha512;
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;
/// Data type for incoming packet buffers.
///
/// 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]>;
/// Data type for giving ZSSP temporary ownership of a buffer containing the local party's
/// identity.
/// 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 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 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.
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-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 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.
///
/// This function must deterministically return either true or false for a given session.
///
/// It is a bad sign that Bob has somehow forgotten Alice's ratchet key, it either means at
/// 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.
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
/// cause us to connect to Alice using the returned ratchet number and ratchet key.
///
/// 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 empty ratchet key, restarting the ratchet chain.
/// If `RatchetAction::FailAuthentication` is returned Alice's connection will be silently dropped.
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.
///
/// 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 empty.
///
/// This function may also save state to volatile storage, in which case all peers which connect
/// 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.
fn save_ratchet_state(
&self,
remote_static_key: &Self::PublicKey,
application_data: &Self::Data,
pre_ratchet_states: [&RatchetState; 2],
new_ratchet_states: [&RatchetState; 2],
current_time: i64,
) -> Result<(), Self::IoError>;
#[allow(unused)]
#[inline]
fn event_log(&self, event: LogEvent<Self>, current_time: i64) {}
}
+108
View File
@@ -0,0 +1,108 @@
use std::hash::Hasher;
use std::sync::atomic::{AtomicU64, Ordering};
use rand_core::{CryptoRng, RngCore};
use crate::antireplay::Window;
use crate::crypto::*;
use crate::proto::*;
pub struct ChallengeContext {
counter: AtomicU64,
antireplay_window: Window<CHALLENGE_COUNTER_WINDOW_MAX_OOO, { u64::MAX }>,
salt: [u8; SALT_SIZE],
}
/// Corresponds to Algorithm 11 found in Section 5.
pub fn gen_null_response(rng: &mut impl RngCore) -> [u8; CHALLENGE_SIZE] {
let mut response = [0u8; CHALLENGE_SIZE];
response[POW_START..].copy_from_slice(&rng.next_u64().to_ne_bytes());
response
}
/// Corresponds to Algorithm 13 found in Section 5.
pub fn respond_to_challenge_in_place(
rng: &mut impl RngCore,
hash: &mut impl Sha512Hash,
challenge: &[u8; CHALLENGE_SIZE],
pre_response: &mut [u8; CHALLENGE_SIZE],
) {
if &challenge[POW_START..] == &pre_response[POW_START..] {
pre_response.copy_from_slice(challenge);
let mut pow = rng.next_u64();
let mut work_buf = [0u8; SHA512_HASH_SIZE];
loop {
pre_response[POW_START..].copy_from_slice(&pow.to_ne_bytes());
if verify_pow(hash, pre_response, &mut work_buf) {
return;
}
pow = pow.wrapping_add(1);
}
}
}
impl ChallengeContext {
pub fn new<Rng: RngCore + CryptoRng>(rng: &mut Rng) -> Self {
let mut salt = [0u8; SALT_SIZE];
rng.fill_bytes(&mut salt);
Self {
counter: AtomicU64::new(0),
antireplay_window: Window::new(),
salt,
}
}
/// Corresponds to Algorithm 12 found in Section 5.
pub fn process_hello(
&self,
hash: &mut impl Sha512Hash,
addr: &impl std::hash::Hash,
response: &[u8; CHALLENGE_SIZE],
) -> Result<(), [u8; CHALLENGE_SIZE]> {
let c = u64::from_be_bytes(response[..COUNTER_SIZE].try_into().unwrap());
let mut work_buf = [0u8; SHA512_HASH_SIZE];
if self.antireplay_window.check(c)
&& secure_eq(&response[COUNTER_SIZE..POW_START], &self.create_mac(hash, c, addr))
&& verify_pow(hash, response, &mut work_buf)
{
self.antireplay_window.update(c);
Ok(())
} else {
let mut challenge = [0u8; CHALLENGE_SIZE];
let d = self.counter.fetch_add(1, Ordering::Relaxed);
challenge[..COUNTER_SIZE].copy_from_slice(&d.to_be_bytes());
challenge[COUNTER_SIZE..POW_START].copy_from_slice(&self.create_mac(hash, d, addr));
challenge[POW_START..].copy_from_slice(&response[POW_START..]);
Err(challenge)
}
}
fn create_mac(&self, hash: &mut impl Sha512Hash, c: u64, addr: &impl std::hash::Hash) -> [u8; MAC_SIZE] {
let mut hasher = ShaHasher(hash);
hasher.write(&c.to_be_bytes());
addr.hash(&mut hasher);
hasher.write(&self.salt);
drop(hasher);
let mut mac = [0u8; SHA512_HASH_SIZE];
hash.finish_and_reset(&mut mac);
mac[..MAC_SIZE].try_into().unwrap()
}
}
/// Trick rust into letting us use a hasher that returns more than 64 bits.
struct ShaHasher<'a, ShaImpl: Sha512Hash>(&'a mut ShaImpl);
impl<'a, ShaImpl: Sha512Hash> Hasher for ShaHasher<'a, ShaImpl> {
fn finish(&self) -> u64 {
unimplemented!()
}
fn write(&mut self, bytes: &[u8]) {
self.0.update(bytes)
}
}
/// Check if the proof of work attached to the first message contains the correct number of leading
/// zeros.
fn verify_pow(hash: &mut impl Sha512Hash, response: &[u8], work_buf: &mut [u8; SHA512_HASH_SIZE]) -> bool {
hash.update(response);
hash.finish_and_reset(work_buf);
let n = u32::from_be_bytes(work_buf[..4].try_into().unwrap());
n.leading_zeros() >= DIFFICULTY
}
+73 -5
View File
@@ -1,20 +1,88 @@
// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md.
pub const AES_256_BLOCK_SIZE: usize = 16;
pub const AES_256_KEY_SIZE: usize = 32;
pub const AES_256_BLOCK_SIZE: usize = 16;
pub const AES_GCM_TAG_SIZE: usize = 16;
pub const AES_GCM_NONCE_SIZE: usize = 12;
pub trait AesEnc: Send + Sync {
/// A trait for encrypting individual blocks of plaintext using AES-256.
/// It is used for header authentication, for which we have a standard model proof that our
/// algorithm is secure.
///
/// Instances must securely delete their keys when dropped or reset.
pub trait Aes256Enc: Sized + Send + Sync {
fn new(key: &[u8; AES_256_KEY_SIZE]) -> Self;
fn reset(&self, key: &[u8; AES_256_KEY_SIZE]);
/// Change the encryption key to `key` so that all future encryption is performed with it.
/// This function is very rarely called so it does not have to be particularly efficient.
fn reset(&mut self, key: &[u8; AES_256_KEY_SIZE]) {
*self = Self::new(key);
}
/// Decrypt the given `block` of plaintext directly using the AES block cipher
/// (i.e. AES-256 in zero-padding ECB mode).
/// The ciphertext should be written directly back out to `block`.
fn encrypt_in_place(&self, block: &mut [u8; AES_256_BLOCK_SIZE]);
}
pub trait AesDec: Send + Sync {
/// A trait for decrypting individual blocks of plaintext using AES-256.
///
/// Instances must securely delete their keys when dropped or reset.
pub trait Aes256Dec: Sized + Send + Sync {
fn new(key: &[u8; AES_256_KEY_SIZE]) -> Self;
fn reset(&self, key: &[u8; AES_256_KEY_SIZE]);
/// Change the decryption key to `key` so that all future decryption is performed with it.
/// This function is very rarely called so it does not have to be particularly efficient.
fn reset(&mut self, key: &[u8; AES_256_KEY_SIZE]) {
*self = Self::new(key);
}
/// Decrypt the given `block` of ciphertext directly using the AES 256 block cipher
/// (i.e. AES-256 in zero-padding ECB mode).
/// The plaintext should be written directly back out to `block`.
fn decrypt_in_place(&self, block: &mut [u8; AES_256_BLOCK_SIZE]);
}
pub trait AesGcmEncContext {
fn encrypt(&mut self, input: &[u8], output: &mut [u8]);
fn finish(self) -> [u8; AES_GCM_TAG_SIZE];
}
pub trait AesGcmDecContext {
fn decrypt_in_place(&mut self, data: &mut [u8]);
#[must_use]
fn finish(self, tag: &[u8; AES_GCM_TAG_SIZE]) -> bool;
}
pub trait HighThroughputAesGcmPool: Send + Sync {
type EncContext<'a>: AesGcmEncContext
where
Self: 'a;
type DecContext<'a>: AesGcmDecContext
where
Self: 'a;
fn new(encrypt_key: &[u8; AES_256_KEY_SIZE], decrypt_key: &[u8; AES_256_KEY_SIZE]) -> Self;
fn start_enc<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> Self::EncContext<'a>;
fn start_dec<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> Self::DecContext<'a>;
}
pub trait LowThroughputAesGcm {
fn encrypt_in_place(
key: &[u8; AES_256_KEY_SIZE],
nonce: &[u8; AES_GCM_NONCE_SIZE],
aad: &[u8],
data: &mut [u8],
) -> [u8; AES_GCM_TAG_SIZE];
#[must_use]
fn decrypt_in_place(
key: &[u8; AES_256_KEY_SIZE],
nonce: &[u8; AES_GCM_NONCE_SIZE],
aad: &[u8],
data: &mut [u8],
tag: &[u8; AES_GCM_TAG_SIZE],
) -> bool;
}
-37
View File
@@ -1,37 +0,0 @@
// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md.
pub const AES_GCM_TAG_SIZE: usize = 16;
pub const AES_GCM_IV_SIZE: usize = 12;
pub const AES_GCM_KEY_SIZE: usize = super::aes::AES_256_KEY_SIZE;
/// Implementations of this trait does not have to be Send + Sync,
/// but if it is wrapped in a `Mutex` it must satisfy the requirements of Send + Sync.
pub trait AesGcmEnc {
fn new(key: &[u8; AES_GCM_KEY_SIZE]) -> Self;
fn set_iv(&mut self, iv: &[u8; AES_GCM_IV_SIZE]);
fn set_aad(&mut self, aad: &[u8]);
fn encrypt(&mut self, input: &[u8], output: &mut [u8]);
fn encrypt_in_place(&mut self, data: &mut [u8]);
fn finish_encrypt(&mut self, output: &mut [u8; AES_GCM_TAG_SIZE]);
}
/// Implementations of this trait does not have to be Send + Sync,
/// but if it is wrapped in a `Mutex` it must satisfy the requirements of Send + Sync.
pub trait AesGcmDec {
fn new(key: &[u8; AES_GCM_KEY_SIZE]) -> Self;
fn set_iv(&mut self, iv: &[u8; AES_GCM_IV_SIZE]);
fn set_aad(&mut self, aad: &[u8]);
fn decrypt(&mut self, input: &[u8], output: &mut [u8]);
fn decrypt_in_place(&mut self, data: &mut [u8]);
fn finish_decrypt(&mut self, expected_tag: &[u8; AES_GCM_TAG_SIZE]) -> bool;
}
+44
View File
@@ -0,0 +1,44 @@
use rand_core::{CryptoRng, RngCore};
/// The size of a Kyber1024 public key, which is 1568 bytes.
pub const KYBER_PUBLIC_KEY_SIZE: usize = 1568;
/// The size of a Kyber1024 KEM ciphertext, which is 1568 bytes.
pub const KYBER_CIPHERTEXT_SIZE: usize = 1568;
/// The size of a Kyber1024 KEM plaintext, which is 32 bytes.
pub const KYBER_PLAINTEXT_SIZE: usize = 32;
/// Instances must securely delete the private key when dropped.
pub trait Kyber1024PrivateKey<Rng: RngCore + CryptoRng>: Sized + Send + Sync {
/// Generate a Kyber1024 private key and public key pair, and return the raw bytes of the public
/// key.
/// The private key will be temporarily held in memory but the public key will be immediately
/// sent to the remote peer.
///
/// This function may use the provided RNG or its own, so long as the output is cryptographically random.
fn generate(rng: &mut Rng) -> (Self, [u8; KYBER_PUBLIC_KEY_SIZE]);
/// Generate a Kyber1024 key encapsulation based on the given `public_key`, and return the
/// raw bytes of the generated ciphertext and plaintext. The ciphertext is immediately sent to
/// the remote peer and the plaintext is immediately hashed, both are quickly deleted.
///
/// This function may use the provided RNG or its own, so long as the output is cryptographically random.
///
/// **CRITICAL**: This must return `None` if the given `public_key` is invalid in any way
/// according to the Kyber1024 spec.
#[must_use]
fn encapsulate(
rng: &mut Rng,
public_key: &[u8; KYBER_PUBLIC_KEY_SIZE],
plaintext_out: &mut [u8; KYBER_PLAINTEXT_SIZE],
) -> Option<[u8; KYBER_CIPHERTEXT_SIZE]>;
/// Decapsulate a Kyber1024 `ciphertext` received from the remote peer, retreiving
/// the raw bytes of the original plaintext. This plaintext is immediately hashed and deleted.
///
/// **CRITICAL**: This must return `None` if the given `ciphertext` is invalid in any way
/// according to the Kyber1024 spec.
#[must_use]
fn decapsulate(
&self,
ciphertext: &[u8; KYBER_CIPHERTEXT_SIZE],
plaintext_out: &mut [u8; KYBER_PLAINTEXT_SIZE],
) -> bool;
}
+26 -7
View File
@@ -1,12 +1,31 @@
// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md.
mod aes;
pub use self::aes::*;
pub mod aes;
pub mod aes_gcm;
pub mod p384;
pub mod secret;
pub mod sha512;
mod p384;
pub use self::p384::*;
mod sha512;
pub use sha512::*;
mod kyber1024;
pub use kyber1024::*;
// 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 arrayvec;
pub use rand_core;
pub use zeroize;
/// Constant time byte slice equality.
pub fn secure_eq<A: AsRef<[u8]> + ?Sized, B: AsRef<[u8]> + ?Sized>(a: &A, b: &B) -> bool {
let (a, b) = (a.as_ref(), b.as_ref());
if a.len() == b.len() {
let mut x = 0u8;
for (aa, bb) in a.iter().zip(b.iter()) {
x |= *aa ^ *bb;
}
x == 0
} else {
false
}
}
+29 -14
View File
@@ -1,31 +1,46 @@
// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md.
use super::rand_core::{CryptoRng, RngCore};
use rand_core::{CryptoRng, RngCore};
/// The size in bytes of a P-384 public key when in compressed SEC1-encoded format.
pub const P384_PUBLIC_KEY_SIZE: usize = 49;
/// The size in bytes of the raw output of ECDH between a P-384 public and private key.
pub const P384_ECDH_SHARED_SECRET_SIZE: usize = 48;
/// A NIST P-384 ECDH/ECDSA public key.
pub trait P384PublicKey: Sized + Send + Sync {
/// Create a p384 public key from raw bytes.
/// Create a P-384 public key from raw bytes.
///
/// **CRITICAL**: This function must return `None` if the input `raw_key` is not on the P-384
/// curve, or if it breaks the P-384 spec in any other way.
fn from_bytes(raw_key: &[u8; P384_PUBLIC_KEY_SIZE]) -> Option<Self>;
/// Get the raw bytes that uniquely define the public key.
fn as_bytes(&self) -> &[u8; P384_PUBLIC_KEY_SIZE];
///
/// This must output the compressed SEC1 NIST encoding of P-384 public keys.
fn to_bytes(&self) -> [u8; P384_PUBLIC_KEY_SIZE];
}
/// A NIST P-384 ECDH/ECDSA public/private key pair.
pub trait P384KeyPair: Send + Sync {
///
/// Instances must securely delete the private key when dropped.
pub trait P384KeyPair<Rng: RngCore + CryptoRng> {
/// The `PublicKeyP384` implementation which matches this `KeyPairP384` implementation.
type PublicKey: P384PublicKey;
type Rng: RngCore + CryptoRng;
/// Randomly generate a new p384 keypair.
/// This function may use the provided RNG or it's own,
/// so long as the produced keys are cryptographically random.
fn generate(rng: &mut Self::Rng) -> Self;
/// Randomly generate a new P-384 keypair.
///
/// This function may use the provided RNG or its own, so long as the output is cryptographically random.
fn generate(rng: &mut Rng) -> Self;
/// Get the raw bytes that uniquely define the public key.
fn public_key_bytes(&self) -> &[u8; P384_PUBLIC_KEY_SIZE];
///
/// This must output the compressed SEC1 NIST encoding of P-384 public keys.
fn public_key_bytes(&self) -> [u8; P384_PUBLIC_KEY_SIZE];
/// Perform ECDH key agreement, returning the raw (un-hashed!) ECDH secret.
fn agree(&self, other_public: &Self::PublicKey, output: &mut [u8; P384_ECDH_SHARED_SECRET_SIZE]) -> bool;
/// Perform ECDH key agreement, writing the raw (un-hashed!) ECDH secret to `ecdh_out`.
///
/// **CRITICAL**: This function must return `false` if key agreement between this private key and
/// the input `public_key` key would result in an invalid, non-standard or predictable ECDH secret.
/// Please refer to the NIST spec for P-384 ECDH key agreement, or better yet use a peer reviewed
/// library that has already implemented this correctly.
#[must_use]
fn agree(&self, public_key: &Self::PublicKey, ecdh_out: &mut [u8; P384_ECDH_SHARED_SECRET_SIZE]) -> bool;
}
-135
View File
@@ -1,135 +0,0 @@
// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md.
use std::convert::TryInto;
/// Constant time byte slice equality.
#[inline]
pub fn secure_eq<A: AsRef<[u8]> + ?Sized, B: AsRef<[u8]> + ?Sized>(a: &A, b: &B) -> bool {
let (a, b) = (a.as_ref(), b.as_ref());
if a.len() == b.len() {
let mut x = 0u8;
for (aa, bb) in a.iter().zip(b.iter()) {
x |= *aa ^ *bb;
}
x == 0
} else {
false
}
}
/// Container for secrets that clears them on drop.
///
/// We can't be totally sure that things like libraries are doing this and it's
/// hard to get every use of a secret anywhere, but using this in our code at
/// least reduces the number of secrets that are left lying around in memory.
///
/// This is generally a low-risk thing since it's process memory that's protected,
/// but it's still not a bad idea due to things like swap or obscure side channel
/// attacks that allow memory to be read.
#[derive(Clone)]
#[repr(transparent)]
pub struct Secret<const L: usize>(pub [u8; L]);
impl<const L: usize> Secret<L> {
/// Create a new all-zero secret.
#[inline(always)]
pub fn new() -> Self {
Self([0_u8; L])
}
/// 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())
}
#[inline(always)]
pub fn as_ptr(&self) -> *const u8 {
self.0.as_ptr()
}
#[inline(always)]
pub fn as_bytes(&self) -> &[u8; L] {
&self.0
}
/// Get the first N bytes of this secret as a fixed length array.
#[inline(always)]
pub fn first_n<const N: usize>(&self) -> &[u8; N] {
assert!(N <= L);
unsafe { &*self.0.as_ptr().cast() }
}
/// Clone the first N bytes of this secret as another secret.
#[inline(always)]
pub fn first_n_clone<const N: usize>(&self) -> Secret<N> {
Secret::<N>(*self.first_n())
}
pub fn overwrite(&mut self, src: &Self) {
self.0.copy_from_slice(&src.0);
}
pub fn overwrite_first_n<const N: usize>(&mut self, src: &Secret<N>) {
let amount = N.min(L);
self.0[..amount].copy_from_slice(&src.0[..amount]);
}
pub fn eq_bytes(&self, other: &[u8]) -> bool {
secure_eq(&self.0, other)
}
}
impl<const L: usize> Drop for Secret<L> {
fn drop(&mut self) {
self.0.fill(0);
}
}
impl<const L: usize> Default for Secret<L> {
#[inline(always)]
fn default() -> Self {
Self([0_u8; L])
}
}
impl<const L: usize> AsRef<[u8]> for Secret<L> {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl<const L: usize> AsRef<[u8; L]> for Secret<L> {
#[inline(always)]
fn as_ref(&self) -> &[u8; L] {
&self.0
}
}
impl<const L: usize> AsMut<[u8]> for Secret<L> {
#[inline(always)]
fn as_mut(&mut self) -> &mut [u8] {
&mut self.0
}
}
impl<const L: usize> AsMut<[u8; L]> for Secret<L> {
#[inline(always)]
fn as_mut(&mut self) -> &mut [u8; L] {
&mut self.0
}
}
impl<const L: usize> PartialEq for Secret<L> {
fn eq(&self, other: &Self) -> bool {
secure_eq(&self.0, &other.0)
}
}
impl<const L: usize> Eq for Secret<L> {}
+16 -30
View File
@@ -2,41 +2,27 @@
pub const SHA512_HASH_SIZE: usize = 64;
/// Opaque SHA-512 implementation.
/// Does not need to be threadsafe.
pub trait Sha512 {
/// Allocate memory on the stack or heap for Sha512.
/// An instance of Sha512 will only ever be held on the stack.
/// A SHA-512 implementation.
pub trait Sha512Hash {
/// Create a new instance of SHA-512 for streaming data to.
fn new() -> Self;
/// Reinitialize the internal state of the hash function for a fresh input.
fn reset(&mut self);
fn update(&mut self, input: &[u8]);
/// Finish hashing the input and write the final hash to output.
///
/// After this function is called, this instance of Sha512 will either be dropped
/// or `reset` will be called.
fn finish(&mut self, output: &mut [u8; SHA512_HASH_SIZE]);
/// Update the instance of SHA-512 with input `data`.
/// This must update the state of SHA-512 as if `data` was appended to the previous input.
fn update(&mut self, data: &[u8]);
/// Finish streaming input and output the final hash.
fn finish_and_reset(&mut self, output: &mut [u8; SHA512_HASH_SIZE]);
}
/// Opaque HMAC-SHA-512 implementation.
/// Does not need to be threadsafe.
pub trait HmacSha512 {
/// Allocate memory on the stack or heap for HmacSha512.
/// An instance of HmacSha512 will only ever be held on the stack.
pub trait Sha512Hmac {
/// Allocate space on the stack or heap for repeated Hmac invocations.
///
/// `reset` will always be called before `update` on a new instance of HmacSha512,
/// to make sure there is always a set key.
/// Many FIPS compliant libraries, namely OpenSSL, require initializing an Hmac context on the
/// heap before operating on it.
/// If you are using a more sane library feel free to make this return an empty type.
fn new() -> Self;
/// Reinitialize the internal state of the hash function for a fresh input.
/// The provided key should replace the previous Hmac key.
fn reset(&mut self, key: &[u8]);
fn update(&mut self, input: &[u8]);
/// Finish hashing the input and write the final hash to output.
///
/// After this function is called, this instance of HmacSha512 will either be dropped
/// or `reset` will be called.
fn finish(&mut self, output: &mut [u8; SHA512_HASH_SIZE]);
/// Pure function for computing a single HMAC Hash. Repeat invocations of this function should
/// have no effect on each other.
fn hash(&mut self, key: &[u8], full_input: &[u8], output: &mut [u8; SHA512_HASH_SIZE]);
}
+39
View File
@@ -0,0 +1,39 @@
use rand_core::{CryptoRng, RngCore};
use zeroize::Zeroizing;
use crate::crypto::*;
/// A wrapper for a buffer the size of a pqc_kyber secret key.
/// The crate `pqc_kyber` is low level and operates directly on buffers of bytes.
pub type CrateKyber1024PrivateKey = Zeroizing<[u8; pqc_kyber::KYBER_SECRETKEYBYTES]>;
impl<Rng: RngCore + CryptoRng> Kyber1024PrivateKey<Rng> for CrateKyber1024PrivateKey {
fn generate(rng: &mut Rng) -> (Self, [u8; KYBER_PUBLIC_KEY_SIZE]) {
// According to the source code this can only fail if the RNG fails.
// Idk why rust allows RNG to fail.
let keypair = pqc_kyber::keypair(rng).unwrap();
(Zeroizing::new(keypair.secret), keypair.public)
}
fn encapsulate(
rng: &mut Rng,
public_key: &[u8; KYBER_PUBLIC_KEY_SIZE],
plaintext_out: &mut [u8; KYBER_PLAINTEXT_SIZE],
) -> Option<[u8; KYBER_CIPHERTEXT_SIZE]> {
let ret;
(ret, *plaintext_out) = pqc_kyber::encapsulate(public_key, rng).ok()?;
Some(ret)
}
fn decapsulate(
&self,
ciphertext: &[u8; KYBER_CIPHERTEXT_SIZE],
plaintext_out: &mut [u8; KYBER_PLAINTEXT_SIZE],
) -> bool {
if let Ok(result) = pqc_kyber::decapsulate(ciphertext, self.as_ref()) {
*plaintext_out = result;
true
} else {
false
}
}
}
+51
View File
@@ -0,0 +1,51 @@
#[cfg(feature = "pqc_kyber")]
mod kyber1024;
#[cfg(feature = "pqc_kyber")]
pub use kyber1024::*;
#[cfg(feature = "pqc_kyber")]
pub use pqc_kyber;
#[cfg(feature = "p384")]
mod p384_impl;
#[cfg(feature = "p384")]
pub use p384;
#[cfg(feature = "p384")]
pub use p384_impl::*;
#[cfg(feature = "sha2")]
mod sha512;
#[cfg(feature = "sha2")]
pub use hmac;
#[cfg(feature = "sha2")]
pub use sha2;
#[cfg(feature = "sha2")]
pub use sha512::*;
#[cfg(feature = "openssl-sys")]
mod openssl;
#[cfg(feature = "openssl-sys")]
pub use openssl::*;
#[cfg(feature = "openssl-sys")]
pub use openssl_sys;
#[cfg(feature = "default-crypto")]
pub trait DefaultCrypto {
type SessionData;
type IncomingPacketBuffer: AsMut<[u8]> + AsRef<[u8]>;
}
#[cfg(feature = "default-crypto")]
impl<C: DefaultCrypto> crate::application::CryptoLayer for C {
type Rng = rand_core::OsRng;
type PrpEnc = OpenSSLAes256Enc;
type PrpDec = OpenSSLAes256Dec;
type Aead = OpenSSLAesGcm;
type AeadPool = OpenSSLAesGcmPool;
type Hash = CrateSha512;
type Hmac = CrateHmacSha512;
type PublicKey = CrateP384PublicKey;
type KeyPair = CrateP384KeyPair;
type Kem = CrateKyber1024PrivateKey;
type SessionData = C::SessionData;
type IncomingPacketBuffer = C::IncomingPacketBuffer;
}

Some files were not shown because too many files have changed in this diff Show More