Merge pull request #3 from zerotier/dev

Added a ping-pong example
This commit is contained in:
Monica Moniot
2023-08-09 16:48:29 -04:00
committed by GitHub
7 changed files with 285 additions and 28 deletions
@@ -19,11 +19,11 @@ use rand_core::OsRng;
use rand_core::RngCore;
use zssp_proto::application::{
AcceptAction, ApplicationLayer, RatchetState, RatchetStates, RatchetUpdate, Settings, RATCHET_SIZE, CryptoLayer,
AcceptAction, ApplicationLayer, CryptoLayer, RatchetState, RatchetStates, RatchetUpdate, Settings, RATCHET_SIZE,
};
use zssp_proto::crypto::P384KeyPair;
use zssp_proto::crypto_impl::{
Aes256Crate, AesGcmCrate, RustKyber1024PrivateKey, P384CrateKeyPair, P384CratePublicKey, Sha512Crate,
Aes256Crate, AesGcmCrate, Kyber1024CratePrivateKey, P384CrateKeyPair, P384CratePublicKey, Sha512Crate,
};
use zssp_proto::Session;
@@ -61,7 +61,7 @@ impl CryptoLayer for TestApplication {
type Hash = Sha512Crate;
type PublicKey = P384CratePublicKey;
type KeyPair = P384CrateKeyPair;
type Kem = RustKyber1024PrivateKey;
type Kem = Kyber1024CratePrivateKey;
type SessionData = u128;
}
@@ -77,7 +77,11 @@ impl ApplicationLayer for &mut TestApplication {
true
}
fn check_accept_session(&mut self, remote_static_key: &P384CratePublicKey, identity: &[u8]) -> AcceptAction<TestApplication> {
fn check_accept_session(
&mut self,
remote_static_key: &P384CratePublicKey,
identity: &[u8],
) -> AcceptAction<TestApplication> {
AcceptAction {
session_data: Some(1),
responder_disallows_downgrade: true,
@@ -305,7 +309,8 @@ fn bob_main(
}
if current_time >= next_service {
next_service = current_time + context.service(&mut bob_app, |_| Some((|b| bob_out.send(b).is_ok(), TEST_MTU)));
next_service =
current_time + context.service(&mut bob_app, |_| Some((|b| bob_out.send(b).is_ok(), TEST_MTU)));
}
}
}
@@ -321,11 +326,7 @@ fn core(time: u64, packet_success_rate: u32) {
};
let bob_keypair = P384CrateKeyPair::generate(&mut OsRng);
let bob_pubkey = bob_keypair.public_key();
let bob_app = TestApplication {
time: Instant::now(),
name: "bob",
ratchets: Ratchets::new(),
};
let bob_app = TestApplication { time: Instant::now(), name: "bob", ratchets: Ratchets::new() };
let (alice_out, bob_in) = mpsc::sync_channel::<Vec<u8>>(256);
let (bob_out, alice_in) = mpsc::sync_channel::<Vec<u8>>(256);
+218
View File
@@ -0,0 +1,218 @@
/* 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::cell::RefCell;
use std::convert::Infallible;
use std::ops::DerefMut;
use std::sync::Arc;
use std::time::Instant;
use zssp_proto::application::{
AcceptAction, ApplicationLayer, CryptoLayer, RatchetState, RatchetStates, RatchetUpdate, RATCHET_SIZE,
};
use zssp_proto::crypto::{rand_core::OsRng, P384KeyPair};
use zssp_proto::crypto_impl::{
Aes256Crate, AesGcmCrate, Kyber1024CratePrivateKey, P384CrateKeyPair, P384CratePublicKey, Sha512Crate,
};
use zssp_proto::{Context, Session};
/// The MTU can go as low as 128 bytes, and it does not have to be constant either!
const TEST_MTU: usize = 512;
struct MyApp {
time: Instant,
remote_peers_session: Option<Arc<Session<Self>>>,
}
/// We specify which crypto implementations to use here, you can use those provided by the
/// `crypto_impl` module or provide your own.
impl CryptoLayer for MyApp {
type Rng = OsRng;
type Prp = Aes256Crate;
type Aead = AesGcmCrate;
type Hash = Sha512Crate;
type PublicKey = P384CratePublicKey;
type KeyPair = P384CrateKeyPair;
type Kem = Kyber1024CratePrivateKey;
type SessionData = ();
}
/// In this example for simplicity we won't be hooking up ratchet keys to a filesystem backend.
/// They are dropped and peers ignore if they are missing.
#[allow(unused)]
impl ApplicationLayer for &mut MyApp {
type Crypto = MyApp;
type StorageError = Infallible;
fn hello_requires_recognized_ratchet(&mut self) -> bool {
false
}
fn initiator_disallows_downgrade(&mut self, session: &Arc<Session<MyApp>>) -> bool {
false
}
fn check_accept_session(&mut self, remote_static_key: &P384CratePublicKey, identity: &[u8]) -> AcceptAction<MyApp> {
self.remote_peers_session.take();
AcceptAction {
session_data: Some(()),
responder_disallows_downgrade: false,
responder_silently_rejects: false,
}
}
fn restore_by_fingerprint(
&mut self,
ratchet_fingerprint: &[u8; RATCHET_SIZE],
) -> Result<Option<RatchetState>, Infallible> {
Ok(None)
}
fn restore_by_identity(
&mut self,
remote_static_key: &P384CratePublicKey,
session_data: &(),
) -> Result<Option<RatchetStates>, Infallible> {
Ok(None)
}
fn save_ratchet_state(
&mut self,
remote_static_key: &P384CratePublicKey,
session_data: &(),
update_data: RatchetUpdate<'_>,
) -> Result<(), Infallible> {
Ok(())
}
fn time(&mut self) -> i64 {
self.time.elapsed().as_millis() as i64
}
}
/// In this example protocol the two peers simply bounce the message "ping" and "pong" back and
/// forth forever.
fn process_message(decrypted_message: &[u8], peer_name: &'static str) -> Option<Vec<u8>> {
match decrypted_message {
b"ping" => {
println!("[{}]: pong", peer_name);
Some(b"pong".to_vec())
}
b"pong" => {
println!("[{}]: ping", peer_name);
Some(b"ping".to_vec())
}
_ => None,
}
}
/// For simplicity's sake this function assumes there is only one remote peer we can talk to.
fn receive(
context: &mut Context<MyApp>,
app: &RefCell<MyApp>,
peer_name: &'static str,
recv_queue: &RefCell<Vec<Vec<u8>>>,
send_queue: &RefCell<Vec<Vec<u8>>>,
) {
if let Some(recv_packet) = recv_queue.borrow_mut().pop() {
let push_onto_send_queue = |packet: Vec<u8>| {
assert!(packet.len() <= TEST_MTU);
send_queue.borrow_mut().push(packet);
true
};
use zssp_proto::result::ReceiveOk::*;
use zssp_proto::result::SessionEvent::*;
let result = context.receive(
app.borrow_mut().deref_mut(),
push_onto_send_queue,
TEST_MTU,
|_| Some((push_onto_send_queue, TEST_MTU)),
&0,
recv_packet,
);
let (session, reply_message) = match result {
Ok(Unassociated) => return,
Ok(Session(session, event)) => match event {
NewSession | NewDowngradedSession => {
println!("[{}]: session received", peer_name);
app.borrow_mut().remote_peers_session = Some(session);
return;
}
Data(data) => {
if let Some(reply) = process_message(&data, peer_name) {
(session, reply)
} else {
return;
}
}
Control => return,
Established => {
println!("[{}]: ping", peer_name);
(session, b"ping".to_vec())
}
Rejected => return,
DowngradedRatchetKey => return,
},
Err(e) => {
println!("ERROR {:?}", e);
return;
}
};
context
.send(&session, push_onto_send_queue, TEST_MTU, reply_message)
.unwrap();
// This example does not properly track time so we just call service on every update.
context.service(app.borrow_mut().deref_mut(), |_| Some((push_onto_send_queue, TEST_MTU)));
}
}
/// We create two peers, Alice and Bob, then we have Alice initiate a ZSSP session with Bob, and
/// then we have Alice send the message "ping" to Bob.
/// Bob replies with the message "pong", which Alice replies to with "ping" and so on forever.
fn main() {
let alice_keypair = P384CrateKeyPair::generate(&mut OsRng);
let alice_app = RefCell::new(MyApp { time: Instant::now(), remote_peers_session: None });
let alice_send_queue = RefCell::new(Vec::<Vec<u8>>::new());
let mut alice_context = Context::<MyApp>::new(alice_keypair, OsRng);
let bob_keypair = P384CrateKeyPair::generate(&mut OsRng);
let bob_pubkey = bob_keypair.public_key();
let bob_app = RefCell::new(MyApp { time: Instant::now(), remote_peers_session: None });
let bob_send_queue = RefCell::new(Vec::<Vec<u8>>::new());
let mut bob_context = Context::<MyApp>::new(bob_keypair, OsRng);
let result = alice_context.open(
alice_app.borrow_mut().deref_mut(),
|packet| {
assert!(packet.len() <= TEST_MTU);
alice_send_queue.borrow_mut().push(packet);
true
},
TEST_MTU,
bob_pubkey,
(),
Vec::new(),
);
alice_app.borrow_mut().remote_peers_session = Some(result.unwrap());
println!("[Alice]: session opened");
for _ in 0..16 {
receive(&mut bob_context, &bob_app, "Bob", &alice_send_queue, &bob_send_queue);
receive(
&mut alice_context,
&alice_app,
"Alice",
&bob_send_queue,
&alice_send_queue,
);
}
}
+7 -2
View File
@@ -183,7 +183,11 @@ pub trait ApplicationLayer: Sized {
/// 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>;
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
@@ -241,7 +245,8 @@ pub trait ApplicationLayer: Sized {
/// 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")]
fn event_log(&mut self, event: LogEvent<'_, Self::Crypto>);
#[allow(unused)]
fn event_log(&mut self, event: LogEvent<'_, Self::Crypto>) {}
}
/// A collection of fields specifying how to complete the key exchange with a specific remote peer,
+21 -6
View File
@@ -101,7 +101,10 @@ impl<Crypto: CryptoLayer> Context<Crypto> {
static_remote_key: Crypto::PublicKey,
session_data: Crypto::SessionData,
identity: Vec<u8>,
) -> Result<Arc<Session<Crypto>>, OpenError<App::StorageError>> where App: ApplicationLayer<Crypto = Crypto>{
) -> Result<Arc<Session<Crypto>>, OpenError<App::StorageError>>
where
App: ApplicationLayer<Crypto = Crypto>,
{
mtu = mtu.max(MIN_TRANSPORT_MTU);
if identity.len() > IDENTITY_MAX_SIZE {
return Err(OpenError::IdentityTooLarge);
@@ -138,7 +141,10 @@ impl<Crypto: CryptoLayer> Context<Crypto> {
send_to: impl FnOnce(&Arc<Session<Crypto>>) -> Option<(SendFn, usize)>,
remote_address: &impl Hash,
raw_fragment: Vec<u8>,
) -> Result<ReceiveOk<Crypto>, ReceiveError<App::StorageError>> where App: ApplicationLayer<Crypto = Crypto> {
) -> Result<ReceiveOk<Crypto>, ReceiveError<App::StorageError>>
where
App: ApplicationLayer<Crypto = Crypto>,
{
use crate::result::FaultType::*;
send_unassociated_mtu = send_unassociated_mtu.max(MIN_TRANSPORT_MTU);
let ctx = &self.0;
@@ -201,7 +207,12 @@ impl<Crypto: CryptoLayer> Context<Crypto> {
let (p, _) = from_nonce(&pn);
let ret = match p {
PACKET_TYPE_DATA => {
received_payload_in_place::<App>(&mut zeta, kid_recv, to_aes_nonce(&pn), &mut assembled_packet)?;
received_payload_in_place::<App>(
&mut zeta,
kid_recv,
to_aes_nonce(&pn),
&mut assembled_packet,
)?;
SessionEvent::Data(assembled_packet)
}
PACKET_TYPE_HANDSHAKE_RESPONSE => {
@@ -334,11 +345,12 @@ impl<Crypto: CryptoLayer> Context<Crypto> {
},
)?;
log!(app, X3IsAuthSentKeyConfirm(&session));
Ok(ReceiveOk::Session(session, if should_warn_missing_ratchet {
let ret = if should_warn_missing_ratchet {
SessionEvent::NewDowngradedSession
} else {
SessionEvent::NewSession
}))
};
Ok(ReceiveOk::Session(session, ret))
} else {
Ok(ReceiveOk::Unassociated)
}
@@ -478,7 +490,10 @@ impl<Crypto: CryptoLayer> Context<Crypto> {
&mut self,
mut app: App,
mut send_to: impl FnMut(&Arc<Session<Crypto>>) -> Option<(SendFn, usize)>,
) -> i64 where App: ApplicationLayer<Crypto = Crypto> {
) -> i64
where
App: ApplicationLayer<Crypto = Crypto>,
{
let ctx = &self.0;
let sessions = ctx.sessions.borrow_mut();
let current_time = app.time();
+2 -2
View File
@@ -5,8 +5,8 @@ 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 RustKyber1024PrivateKey = Zeroizing<[u8; pqc_kyber::KYBER_SECRETKEYBYTES]>;
impl<Rng: RngCore + CryptoRng> Kyber1024PrivateKey<Rng> for RustKyber1024PrivateKey {
pub type Kyber1024CratePrivateKey = Zeroizing<[u8; pqc_kyber::KYBER_SECRETKEYBYTES]>;
impl<Rng: RngCore + CryptoRng> Kyber1024PrivateKey<Rng> for Kyber1024CratePrivateKey {
fn generate(rng: &mut Rng) -> (Self, [u8; KYBER_PUBLIC_KEY_SIZE]) {
let keypair = pqc_kyber::keypair(rng);
(Zeroizing::new(keypair.secret), keypair.public)
+1 -1
View File
@@ -4,7 +4,7 @@ use std::collections::HashMap;
use zeroize::Zeroizing;
use crate::application::{CryptoLayer, ApplicationLayer};
use crate::application::{ApplicationLayer, CryptoLayer};
use crate::crypto::{Aes256Prp, AES_256_KEY_SIZE};
use crate::proto::*;
use crate::result::{byzantine_fault, ReceiveError};
+25 -7
View File
@@ -7,7 +7,7 @@ use std::sync::{Arc, Weak};
use rand_core::RngCore;
use zeroize::Zeroizing;
use crate::application::{ApplicationLayer, RatchetState, RatchetStates, RatchetUpdate, CryptoLayer};
use crate::application::{ApplicationLayer, CryptoLayer, RatchetState, RatchetStates, RatchetUpdate};
use crate::challenge::{gen_null_response, respond_to_challenge_in_place};
use crate::context::{log, ContextInner, SessionMap};
use crate::crypto::*;
@@ -700,8 +700,8 @@ pub(crate) fn received_x3_trans<App: ApplicationLayer>(
if !noise.decrypt_and_hash_in_place(to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 1), &mut x3[i..j], tag) {
return Err(byzantine_fault!(FailedAuth, true));
}
let s_remote =
<App::Crypto as CryptoLayer>::PublicKey::from_bytes((&x3[i..j]).try_into().unwrap()).ok_or(byzantine_fault!(FailedAuth, true))?;
let s_remote = <App::Crypto as CryptoLayer>::PublicKey::from_bytes((&x3[i..j]).try_into().unwrap())
.ok_or(byzantine_fault!(FailedAuth, true))?;
i = k;
// Process message pattern 3 se token.
noise
@@ -888,7 +888,10 @@ pub(crate) fn received_c1_trans<App: ApplicationLayer>(
zeta.ratchet_state2 = None;
zeta.key_index ^= true;
let r = rng.borrow_mut().next_u64() % <App::Crypto as CryptoLayer>::SETTINGS.rekey_time_max_jitter;
zeta.timeout_timer = app.time() + <App::Crypto as CryptoLayer>::SETTINGS.rekey_after_time.saturating_sub(r) as i64;
zeta.timeout_timer = app.time()
+ <App::Crypto as CryptoLayer>::SETTINGS
.rekey_after_time
.saturating_sub(r) as i64;
zeta.resend_timer = i64::MAX;
zeta.beta = ZetaAutomata::S2;
}
@@ -925,7 +928,13 @@ pub(crate) fn received_c2_trans<App: ApplicationLayer>(
}
let tag = c2[..].try_into().unwrap();
if !<App::Crypto as CryptoLayer>::Aead::decrypt_in_place(zeta.key_ref(false).recv.kek.as_ref().unwrap(), &n, None, &mut [], tag) {
if !<App::Crypto as CryptoLayer>::Aead::decrypt_in_place(
zeta.key_ref(false).recv.kek.as_ref().unwrap(),
&n,
None,
&mut [],
tag,
) {
return Err(byzantine_fault!(FailedAuth, true));
}
let (_, c) = from_nonce(&n);
@@ -934,7 +943,10 @@ pub(crate) fn received_c2_trans<App: ApplicationLayer>(
}
let r = rng.borrow_mut().next_u64() % <App::Crypto as CryptoLayer>::SETTINGS.rekey_time_max_jitter;
zeta.timeout_timer = app.time() + <App::Crypto as CryptoLayer>::SETTINGS.rekey_after_time.saturating_sub(r) as i64;
zeta.timeout_timer = app.time()
+ <App::Crypto as CryptoLayer>::SETTINGS
.rekey_after_time
.saturating_sub(r) as i64;
zeta.resend_timer = i64::MAX;
zeta.beta = ZetaAutomata::S2;
Ok(())
@@ -957,7 +969,13 @@ pub(crate) fn received_d_trans<App: ApplicationLayer>(
}
let tag = d[..].try_into().unwrap();
if !<App::Crypto as CryptoLayer>::Aead::decrypt_in_place(zeta.key_ref(true).recv.kek.as_ref().unwrap(), &n, None, &mut [], tag) {
if !<App::Crypto as CryptoLayer>::Aead::decrypt_in_place(
zeta.key_ref(true).recv.kek.as_ref().unwrap(),
&n,
None,
&mut [],
tag,
) {
return Err(byzantine_fault!(FailedAuth, true));
}
let (_, c) = from_nonce(&n);