mirror of
https://github.com/zerotier/zssp.git
synced 2026-05-22 16:28:40 -07:00
added the ability to mutate app
This commit is contained in:
+44
-44
@@ -7,10 +7,11 @@
|
||||
*/
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::convert::Infallible;
|
||||
use std::iter::ExactSizeIterator;
|
||||
use std::str::FromStr;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{mpsc, Arc, Mutex};
|
||||
use std::sync::{mpsc, Arc};
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
@@ -18,7 +19,7 @@ use rand_core::OsRng;
|
||||
use rand_core::RngCore;
|
||||
|
||||
use zssp_proto::application::{
|
||||
AcceptAction, ApplicationLayer, RatchetState, RatchetStates, RatchetUpdate, Settings, RATCHET_SIZE,
|
||||
AcceptAction, ApplicationLayer, RatchetState, RatchetStates, RatchetUpdate, Settings, RATCHET_SIZE, CryptoLayer,
|
||||
};
|
||||
use zssp_proto::crypto::P384KeyPair;
|
||||
use zssp_proto::crypto_impl::{
|
||||
@@ -31,7 +32,7 @@ const TEST_MTU: usize = 1500;
|
||||
struct TestApplication {
|
||||
time: Instant,
|
||||
name: &'static str,
|
||||
ratchets: Mutex<Ratchets>,
|
||||
ratchets: Ratchets,
|
||||
}
|
||||
|
||||
struct Ratchets {
|
||||
@@ -44,8 +45,7 @@ impl Ratchets {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
impl ApplicationLayer for &TestApplication {
|
||||
impl CryptoLayer for TestApplication {
|
||||
const SETTINGS: Settings = Settings {
|
||||
initial_offer_timeout: Settings::INITIAL_OFFER_TIMEOUT_MS,
|
||||
rekey_timeout: 60 * 1000,
|
||||
@@ -63,18 +63,21 @@ impl ApplicationLayer for &TestApplication {
|
||||
type KeyPair = P384CrateKeyPair;
|
||||
type Kem = RustKyber1024PrivateKey;
|
||||
|
||||
type StorageError = std::convert::Infallible;
|
||||
type SessionData = u128;
|
||||
|
||||
fn hello_requires_recognized_ratchet(&self) -> bool {
|
||||
}
|
||||
#[allow(unused)]
|
||||
impl ApplicationLayer for &mut TestApplication {
|
||||
type Crypto = TestApplication;
|
||||
type StorageError = Infallible;
|
||||
fn hello_requires_recognized_ratchet(&mut self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn initiator_disallows_downgrade(&self, session: &Arc<Session<Self>>) -> bool {
|
||||
fn initiator_disallows_downgrade(&mut self, session: &Arc<Session<TestApplication>>) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn check_accept_session(&self, remote_static_key: &Self::PublicKey, identity: &[u8]) -> AcceptAction<Self> {
|
||||
fn check_accept_session(&mut self, remote_static_key: &P384CratePublicKey, identity: &[u8]) -> AcceptAction<TestApplication> {
|
||||
AcceptAction {
|
||||
session_data: Some(1),
|
||||
responder_disallows_downgrade: true,
|
||||
@@ -83,49 +86,46 @@ impl ApplicationLayer for &TestApplication {
|
||||
}
|
||||
|
||||
fn restore_by_fingerprint(
|
||||
&self,
|
||||
&mut self,
|
||||
ratchet_fingerprint: &[u8; RATCHET_SIZE],
|
||||
) -> Result<Option<RatchetState>, Self::StorageError> {
|
||||
let ratchets = self.ratchets.lock().unwrap();
|
||||
Ok(ratchets.rf_map.get(ratchet_fingerprint).cloned())
|
||||
) -> Result<Option<RatchetState>, Infallible> {
|
||||
Ok(self.ratchets.rf_map.get(ratchet_fingerprint).cloned())
|
||||
}
|
||||
|
||||
fn restore_by_identity(
|
||||
&self,
|
||||
remote_static_key: &Self::PublicKey,
|
||||
session_data: &Self::SessionData,
|
||||
) -> Result<Option<RatchetStates>, Self::StorageError> {
|
||||
let ratchets = self.ratchets.lock().unwrap();
|
||||
Ok(ratchets.peer_map.get(session_data).cloned())
|
||||
&mut self,
|
||||
remote_static_key: &P384CratePublicKey,
|
||||
session_data: &u128,
|
||||
) -> Result<Option<RatchetStates>, Infallible> {
|
||||
Ok(self.ratchets.peer_map.get(session_data).cloned())
|
||||
}
|
||||
|
||||
fn save_ratchet_state(
|
||||
&self,
|
||||
remote_static_key: &Self::PublicKey,
|
||||
session_data: &Self::SessionData,
|
||||
&mut self,
|
||||
remote_static_key: &P384CratePublicKey,
|
||||
session_data: &u128,
|
||||
update_data: RatchetUpdate<'_>,
|
||||
) -> Result<(), Self::StorageError> {
|
||||
let mut ratchets = self.ratchets.lock().unwrap();
|
||||
ratchets.peer_map.insert(*session_data, update_data.to_states());
|
||||
) -> Result<(), Infallible> {
|
||||
self.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());
|
||||
self.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);
|
||||
self.ratchets.rf_map.remove(rf);
|
||||
}
|
||||
if let Some(rf) = update_data.deleted_fingerprint2() {
|
||||
ratchets.rf_map.remove(rf);
|
||||
self.ratchets.rf_map.remove(rf);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn time(&self) -> i64 {
|
||||
fn time(&mut self) -> i64 {
|
||||
self.time.elapsed().as_millis() as i64
|
||||
}
|
||||
|
||||
fn event_log(&self, event: zssp_proto::LogEvent<Self>) {
|
||||
fn event_log(&mut self, event: zssp_proto::LogEvent<TestApplication>) {
|
||||
println!(">[{}] {:?}", self.name, event);
|
||||
}
|
||||
}
|
||||
@@ -133,7 +133,7 @@ impl ApplicationLayer for &TestApplication {
|
||||
fn alice_main(
|
||||
run: &AtomicBool,
|
||||
packet_success_rate: u32,
|
||||
alice_app: &TestApplication,
|
||||
mut alice_app: TestApplication,
|
||||
alice_out: mpsc::SyncSender<Vec<u8>>,
|
||||
alice_in: mpsc::Receiver<Vec<u8>>,
|
||||
recursive_out: mpsc::SyncSender<Vec<u8>>,
|
||||
@@ -141,7 +141,7 @@ fn alice_main(
|
||||
bob_pubkey: P384CratePublicKey,
|
||||
) {
|
||||
let startup_time = std::time::Instant::now();
|
||||
let mut context = zssp_proto::Context::<&TestApplication>::new(alice_keypair, OsRng);
|
||||
let mut context = zssp_proto::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;
|
||||
@@ -153,7 +153,7 @@ fn alice_main(
|
||||
alice_session = Some(
|
||||
context
|
||||
.open(
|
||||
alice_app,
|
||||
&mut alice_app,
|
||||
|b| alice_out.send(b).is_ok(),
|
||||
TEST_MTU,
|
||||
bob_pubkey.clone(),
|
||||
@@ -173,7 +173,7 @@ fn alice_main(
|
||||
use zssp_proto::result::ReceiveOk::*;
|
||||
use zssp_proto::result::SessionEvent::*;
|
||||
match context.receive(
|
||||
alice_app,
|
||||
&mut alice_app,
|
||||
|b| alice_out.send(b).is_ok(),
|
||||
TEST_MTU,
|
||||
|_| Some((|b| alice_out.send(b).is_ok(), TEST_MTU)),
|
||||
@@ -228,7 +228,7 @@ fn alice_main(
|
||||
|
||||
if current_time >= next_service {
|
||||
next_service =
|
||||
current_time + context.service(alice_app, |_| Some((|b| alice_out.send(b).is_ok(), TEST_MTU)));
|
||||
current_time + context.service(&mut alice_app, |_| Some((|b| alice_out.send(b).is_ok(), TEST_MTU)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -236,14 +236,14 @@ fn alice_main(
|
||||
fn bob_main(
|
||||
run: &AtomicBool,
|
||||
packet_success_rate: u32,
|
||||
bob_app: &TestApplication,
|
||||
mut bob_app: TestApplication,
|
||||
bob_out: mpsc::SyncSender<Vec<u8>>,
|
||||
bob_in: mpsc::Receiver<Vec<u8>>,
|
||||
recursive_out: mpsc::SyncSender<Vec<u8>>,
|
||||
bob_keypair: P384CrateKeyPair,
|
||||
) {
|
||||
let startup_time = std::time::Instant::now();
|
||||
let mut context = zssp_proto::Context::<&TestApplication>::new(bob_keypair, OsRng);
|
||||
let mut context = zssp_proto::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;
|
||||
@@ -260,7 +260,7 @@ fn bob_main(
|
||||
use zssp_proto::result::ReceiveOk::*;
|
||||
use zssp_proto::result::SessionEvent::*;
|
||||
match context.receive(
|
||||
bob_app,
|
||||
&mut bob_app,
|
||||
|b| bob_out.send(b).is_ok(),
|
||||
TEST_MTU,
|
||||
|_| Some((|b| bob_out.send(b).is_ok(), TEST_MTU)),
|
||||
@@ -305,7 +305,7 @@ fn bob_main(
|
||||
}
|
||||
|
||||
if current_time >= next_service {
|
||||
next_service = current_time + context.service(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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -317,14 +317,14 @@ fn core(time: u64, packet_success_rate: u32) {
|
||||
let alice_app = TestApplication {
|
||||
time: Instant::now(),
|
||||
name: "alice",
|
||||
ratchets: Mutex::new(Ratchets::new()),
|
||||
ratchets: Ratchets::new(),
|
||||
};
|
||||
let bob_keypair = P384CrateKeyPair::generate(&mut OsRng);
|
||||
let bob_pubkey = bob_keypair.public_key();
|
||||
let bob_app = TestApplication {
|
||||
time: Instant::now(),
|
||||
name: "bob",
|
||||
ratchets: Mutex::new(Ratchets::new()),
|
||||
ratchets: Ratchets::new(),
|
||||
};
|
||||
|
||||
let (alice_out, bob_in) = mpsc::sync_channel::<Vec<u8>>(256);
|
||||
@@ -338,7 +338,7 @@ fn core(time: u64, packet_success_rate: u32) {
|
||||
alice_main(
|
||||
run,
|
||||
packet_success_rate,
|
||||
&alice_app,
|
||||
alice_app,
|
||||
alice_out,
|
||||
alice_in,
|
||||
bob_out,
|
||||
@@ -351,7 +351,7 @@ fn core(time: u64, packet_success_rate: u32) {
|
||||
bob_main(
|
||||
run,
|
||||
packet_success_rate,
|
||||
&bob_app,
|
||||
bob_app,
|
||||
bob_out,
|
||||
bob_in,
|
||||
alice_out,
|
||||
|
||||
+23
-16
@@ -83,11 +83,11 @@ impl Default for Settings {
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait to implement to integrate the session into an application.
|
||||
/// Trait to implement to integrate ZSSP into an application.
|
||||
///
|
||||
/// Templating the session on this trait lets the code here be almost entirely transport, OS,
|
||||
/// and use case independent.
|
||||
pub trait ApplicationLayer: Sized {
|
||||
/// This is a container trait for all of the cryptographic algorithms ZSSP will use, and all of the
|
||||
/// generic types that ZSSP will attach to sessions.
|
||||
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.
|
||||
@@ -131,13 +131,20 @@ pub trait ApplicationLayer: Sized {
|
||||
/// for ZSSP to achieve FIPS compliance.
|
||||
type Kem: Kyber1024PrivateKey<Self::Rng>;
|
||||
|
||||
/// An arbitrary opaque object for use by the application that is attached to each session.
|
||||
type SessionData;
|
||||
}
|
||||
/// Trait to implement to integrate ZSSP into an application.
|
||||
///
|
||||
/// Templating ZSSP on this trait lets the code here be almost entirely transport, OS,
|
||||
/// and use case independent.
|
||||
pub trait ApplicationLayer: Sized {
|
||||
/// Specifies which concrete set of cryptography types will be used by this application.
|
||||
type Crypto: CryptoLayer;
|
||||
/// A user-defined error returned when the `ApplicationLayer` fails to access persistent storage
|
||||
/// for a peer's ratchet states.
|
||||
type StorageError: std::error::Error;
|
||||
|
||||
/// An arbitrary opaque object for use by the application that is attached to each session.
|
||||
type SessionData;
|
||||
|
||||
/// 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
|
||||
@@ -169,14 +176,14 @@ 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.
|
||||
fn initiator_disallows_downgrade(&mut self, session: &Arc<Session<Self>>) -> bool;
|
||||
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::PublicKey, identity: &[u8]) -> AcceptAction<Self>;
|
||||
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
|
||||
@@ -204,8 +211,8 @@ pub trait ApplicationLayer: Sized {
|
||||
/// function `ApplicationLayer::check_accept_session`.
|
||||
fn restore_by_identity(
|
||||
&mut self,
|
||||
remote_static_key: &Self::PublicKey,
|
||||
session_data: &Self::SessionData,
|
||||
remote_static_key: &<Self::Crypto as CryptoLayer>::PublicKey,
|
||||
session_data: &<Self::Crypto as CryptoLayer>::SessionData,
|
||||
) -> Result<Option<RatchetStates>, Self::StorageError>;
|
||||
/// Atomically commit the update specified by `update_data` to storage, or return an error if
|
||||
/// the update could not be made.
|
||||
@@ -225,8 +232,8 @@ pub trait ApplicationLayer: Sized {
|
||||
/// Otherwise, when we restart, we will not be allowed to reconnect.
|
||||
fn save_ratchet_state(
|
||||
&mut self,
|
||||
remote_static_key: &Self::PublicKey,
|
||||
session_data: &Self::SessionData,
|
||||
remote_static_key: &<Self::Crypto as CryptoLayer>::PublicKey,
|
||||
session_data: &<Self::Crypto as CryptoLayer>::SessionData,
|
||||
update_data: RatchetUpdate<'_>,
|
||||
) -> Result<(), Self::StorageError>;
|
||||
|
||||
@@ -234,17 +241,17 @@ 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>);
|
||||
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,
|
||||
/// 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<App: ApplicationLayer> {
|
||||
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<App::SessionData>,
|
||||
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,
|
||||
|
||||
+39
-39
@@ -6,7 +6,7 @@ use std::hash::Hash;
|
||||
use std::num::NonZeroU32;
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use crate::application::ApplicationLayer;
|
||||
use crate::application::{ApplicationLayer, CryptoLayer};
|
||||
use crate::challenge::ChallengeContext;
|
||||
use crate::crypto::{AES_256_KEY_SIZE, AES_GCM_NONCE_SIZE};
|
||||
use crate::fragmentation::{send_with_fragmentation, DefragBuffer};
|
||||
@@ -31,21 +31,21 @@ pub(crate) use log;
|
||||
/// defragment incoming packets that are not yet associated with a session.
|
||||
///
|
||||
/// Internally this is just a clonable Arc, so it can be safely shared with multiple threads.
|
||||
pub struct Context<App: ApplicationLayer>(Arc<ContextInner<App>>);
|
||||
impl<App: ApplicationLayer> Clone for Context<App> {
|
||||
pub struct Context<Crypto: CryptoLayer>(Arc<ContextInner<Crypto>>);
|
||||
impl<Crypto: CryptoLayer> Clone for Context<Crypto> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type SessionMap<App> = RefCell<HashMap<NonZeroU32, Weak<Session<App>>>>;
|
||||
pub(crate) type SessionMap<Crypto> = RefCell<HashMap<NonZeroU32, Weak<Session<Crypto>>>>;
|
||||
|
||||
pub(crate) struct ContextInner<App: ApplicationLayer> {
|
||||
pub(crate) rng: RefCell<App::Rng>,
|
||||
pub(crate) s_secret: App::KeyPair,
|
||||
pub(crate) session_map: SessionMap<App>,
|
||||
pub(crate) sessions: RefCell<HashMap<*const Session<App>, Weak<Session<App>>>>,
|
||||
pub(crate) b2_map: RefCell<HashMap<NonZeroU32, StateB2<App>>>,
|
||||
pub(crate) struct ContextInner<Crypto: CryptoLayer> {
|
||||
pub(crate) rng: RefCell<Crypto::Rng>,
|
||||
pub(crate) s_secret: Crypto::KeyPair,
|
||||
pub(crate) session_map: SessionMap<Crypto>,
|
||||
pub(crate) sessions: RefCell<HashMap<*const Session<Crypto>, Weak<Session<Crypto>>>>,
|
||||
pub(crate) b2_map: RefCell<HashMap<NonZeroU32, StateB2<Crypto>>>,
|
||||
|
||||
hello_defrag: RefCell<DefragBuffer>,
|
||||
challenge: RefCell<ChallengeContext>,
|
||||
@@ -62,9 +62,9 @@ fn to_packet_nonce(n: &[u8; AES_GCM_NONCE_SIZE]) -> &[u8; PACKET_NONCE_SIZE] {
|
||||
(&n[n.len() - PACKET_NONCE_SIZE..]).try_into().unwrap()
|
||||
}
|
||||
|
||||
impl<App: ApplicationLayer> Context<App> {
|
||||
impl<Crypto: CryptoLayer> Context<Crypto> {
|
||||
/// Create a new session context.
|
||||
pub fn new(static_secret_key: App::KeyPair, mut rng: App::Rng) -> Self {
|
||||
pub fn new(static_secret_key: Crypto::KeyPair, mut rng: Crypto::Rng) -> Self {
|
||||
let challenge = ChallengeContext::new(&mut rng);
|
||||
Self(Arc::new(ContextInner {
|
||||
rng: RefCell::new(rng),
|
||||
@@ -93,15 +93,15 @@ impl<App: ApplicationLayer> Context<App> {
|
||||
/// object
|
||||
/// * `identity` - Payload to be sent to Bob that contains the information necessary
|
||||
/// for the upper protocol to authenticate and approve of Alice's identity
|
||||
pub fn open(
|
||||
pub fn open<App: ApplicationLayer>(
|
||||
&mut self,
|
||||
app: App,
|
||||
send: impl FnMut(Vec<u8>) -> bool,
|
||||
mut mtu: usize,
|
||||
static_remote_key: App::PublicKey,
|
||||
session_data: App::SessionData,
|
||||
static_remote_key: Crypto::PublicKey,
|
||||
session_data: Crypto::SessionData,
|
||||
identity: Vec<u8>,
|
||||
) -> Result<Arc<Session<App>>, OpenError<App::StorageError>> {
|
||||
) -> 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);
|
||||
@@ -111,13 +111,13 @@ impl<App: ApplicationLayer> Context<App> {
|
||||
// Process zeta layer.
|
||||
trans_to_a1(
|
||||
app,
|
||||
&ctx,
|
||||
ctx,
|
||||
static_remote_key,
|
||||
session_data,
|
||||
identity,
|
||||
|Packet(kid, nonce, payload): &Packet| {
|
||||
// Process fragmentation layer.
|
||||
send_with_fragmentation::<App>(send, mtu, *kid, to_packet_nonce(&nonce), payload, None);
|
||||
send_with_fragmentation::<Crypto>(send, mtu, *kid, to_packet_nonce(&nonce), payload, None);
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -130,15 +130,15 @@ impl<App: ApplicationLayer> Context<App> {
|
||||
/// * `send_to` - Function to get senders for existing sessions, permitting MTU and path lookup
|
||||
/// * `remote_address` - Whatever the remote address is, as long as you can Hash it
|
||||
/// * `raw_fragment` - Buffer containing incoming wire packet
|
||||
pub fn receive<SendFn: FnMut(Vec<u8>) -> bool>(
|
||||
pub fn receive<App, SendFn: FnMut(Vec<u8>) -> bool>(
|
||||
&mut self,
|
||||
mut app: App,
|
||||
send_unassociated_reply: impl FnMut(Vec<u8>) -> bool,
|
||||
mut send_unassociated_mtu: usize,
|
||||
send_to: impl FnOnce(&Arc<Session<App>>) -> Option<(SendFn, usize)>,
|
||||
send_to: impl FnOnce(&Arc<Session<Crypto>>) -> Option<(SendFn, usize)>,
|
||||
remote_address: &impl Hash,
|
||||
raw_fragment: Vec<u8>,
|
||||
) -> Result<ReceiveOk<App>, ReceiveError<App::StorageError>> {
|
||||
) -> 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;
|
||||
@@ -187,7 +187,7 @@ impl<App: ApplicationLayer> Context<App> {
|
||||
|Packet(kid, nonce, payload): &Packet, hk: Option<&[u8; AES_256_KEY_SIZE]>| {
|
||||
if let Some((send_fragment, mut mtu)) = send_to(&session) {
|
||||
mtu = mtu.max(MIN_TRANSPORT_MTU);
|
||||
send_with_fragmentation::<App>(
|
||||
send_with_fragmentation::<Crypto>(
|
||||
send_fragment,
|
||||
mtu,
|
||||
*kid,
|
||||
@@ -201,7 +201,7 @@ impl<App: ApplicationLayer> Context<App> {
|
||||
let (p, _) = from_nonce(&pn);
|
||||
let ret = match p {
|
||||
PACKET_TYPE_DATA => {
|
||||
received_payload_in_place(&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 => {
|
||||
@@ -286,7 +286,7 @@ impl<App: ApplicationLayer> Context<App> {
|
||||
}
|
||||
PACKET_TYPE_SESSION_REJECTED => {
|
||||
log!(app, ReceivedRawD);
|
||||
received_d_trans(&mut zeta, kid_recv, to_aes_nonce(&pn), assembled_packet)?;
|
||||
received_d_trans::<App>(&mut zeta, kid_recv, to_aes_nonce(&pn), assembled_packet)?;
|
||||
log!(app, DIsAuthClosedSession(&session));
|
||||
SessionEvent::Rejected
|
||||
}
|
||||
@@ -323,7 +323,7 @@ impl<App: ApplicationLayer> Context<App> {
|
||||
kid_recv,
|
||||
assembled_packet,
|
||||
|Packet(kid, nonce, payload), hk| {
|
||||
send_with_fragmentation::<App>(
|
||||
send_with_fragmentation::<Crypto>(
|
||||
send_unassociated_reply,
|
||||
send_unassociated_mtu,
|
||||
*kid,
|
||||
@@ -369,7 +369,7 @@ impl<App: ApplicationLayer> Context<App> {
|
||||
log!(app, ReceivedRawX1);
|
||||
// Process recv challenge layer.
|
||||
let challenge_start = assembled_packet.len() - CHALLENGE_SIZE;
|
||||
let result = ctx.challenge.borrow_mut().process_hello::<App::Hash>(
|
||||
let result = ctx.challenge.borrow_mut().process_hello::<Crypto::Hash>(
|
||||
remote_address,
|
||||
(&assembled_packet[challenge_start..]).try_into().unwrap(),
|
||||
);
|
||||
@@ -379,7 +379,7 @@ impl<App: ApplicationLayer> Context<App> {
|
||||
challenge_packet.extend(&assembled_packet[..KID_SIZE]);
|
||||
challenge_packet.extend(&challenge);
|
||||
let nonce = to_nonce(PACKET_TYPE_CHALLENGE, ctx.rng.borrow_mut().next_u64());
|
||||
send_with_fragmentation::<App>(
|
||||
send_with_fragmentation::<Crypto>(
|
||||
send_unassociated_reply,
|
||||
send_unassociated_mtu,
|
||||
0,
|
||||
@@ -401,7 +401,7 @@ impl<App: ApplicationLayer> Context<App> {
|
||||
to_aes_nonce(&n),
|
||||
assembled_packet,
|
||||
|Packet(kid, nonce, payload), hk| {
|
||||
send_with_fragmentation::<App>(
|
||||
send_with_fragmentation::<Crypto>(
|
||||
send_unassociated_reply,
|
||||
send_unassociated_mtu,
|
||||
*kid,
|
||||
@@ -424,7 +424,7 @@ impl<App: ApplicationLayer> Context<App> {
|
||||
{
|
||||
if let Some(Some(session)) = ctx.session_map.borrow_mut().get(&kid_recv).map(|r| r.upgrade()) {
|
||||
let mut zeta = session.0.borrow_mut();
|
||||
respond_to_challenge(
|
||||
respond_to_challenge::<App>(
|
||||
&mut zeta,
|
||||
&ctx.rng,
|
||||
&assembled_packet[KID_SIZE..].try_into().unwrap(),
|
||||
@@ -454,7 +454,7 @@ impl<App: ApplicationLayer> Context<App> {
|
||||
/// * `payload` - Data to send
|
||||
pub fn send(
|
||||
&mut self,
|
||||
session: &Arc<Session<App>>,
|
||||
session: &Arc<Session<Crypto>>,
|
||||
send: impl FnMut(Vec<u8>) -> bool,
|
||||
mut mtu: usize,
|
||||
payload: Vec<u8>,
|
||||
@@ -462,8 +462,8 @@ impl<App: ApplicationLayer> Context<App> {
|
||||
debug_assert_eq!(session.0.borrow().ctx.as_ptr(), Arc::as_ptr(&self.0));
|
||||
mtu = mtu.max(MIN_TRANSPORT_MTU);
|
||||
let mut zeta = session.0.borrow_mut();
|
||||
send_payload(&mut zeta, payload, |Packet(kid, nonce, payload), hk| {
|
||||
send_with_fragmentation::<App>(send, mtu, *kid, to_packet_nonce(nonce), &payload, hk);
|
||||
send_payload::<Crypto>(&mut zeta, payload, |Packet(kid, nonce, payload), hk| {
|
||||
send_with_fragmentation::<Crypto>(send, mtu, *kid, to_packet_nonce(nonce), &payload, hk);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -474,11 +474,11 @@ impl<App: ApplicationLayer> Context<App> {
|
||||
/// a problem. It is completely fine to call this function more often than the returned interval.
|
||||
///
|
||||
/// * `send_to` - Function to get a sender and an MTU to send something over an active session
|
||||
pub fn service<SendFn: FnMut(Vec<u8>) -> bool>(
|
||||
pub fn service<App, SendFn: FnMut(Vec<u8>) -> bool>(
|
||||
&mut self,
|
||||
mut app: App,
|
||||
mut send_to: impl FnMut(&Arc<Session<App>>) -> Option<(SendFn, usize)>,
|
||||
) -> i64 {
|
||||
mut send_to: impl FnMut(&Arc<Session<Crypto>>) -> Option<(SendFn, usize)>,
|
||||
) -> i64 where App: ApplicationLayer<Crypto = Crypto> {
|
||||
let ctx = &self.0;
|
||||
let sessions = ctx.sessions.borrow_mut();
|
||||
let current_time = app.time();
|
||||
@@ -495,7 +495,7 @@ impl<App: ApplicationLayer> Context<App> {
|
||||
|Packet(kid, nonce, payload): &Packet, hk| {
|
||||
if let Some((send_fragment, mut mtu)) = send_to(&session) {
|
||||
mtu = mtu.max(MIN_TRANSPORT_MTU);
|
||||
send_with_fragmentation::<App>(
|
||||
send_with_fragmentation::<Crypto>(
|
||||
send_fragment,
|
||||
mtu,
|
||||
*kid,
|
||||
@@ -507,10 +507,10 @@ impl<App: ApplicationLayer> Context<App> {
|
||||
},
|
||||
);
|
||||
next_timer = next_timer.min(zeta.next_timer());
|
||||
zeta.defrag.service::<App>(current_time);
|
||||
zeta.defrag.service(current_time);
|
||||
}
|
||||
}
|
||||
ctx.hello_defrag.borrow_mut().service::<App>(current_time);
|
||||
(App::SETTINGS.resend_time as i64).min(next_timer - current_time)
|
||||
ctx.hello_defrag.borrow_mut().service(current_time);
|
||||
(Crypto::SETTINGS.resend_time as i64).min(next_timer - current_time)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,3 +27,38 @@ pub use p384;
|
||||
pub use pqc_kyber;
|
||||
#[cfg(feature = "sha2")]
|
||||
pub use sha2;
|
||||
|
||||
/*
|
||||
TODO: wrangle the feature flags so we can provide the default set of crypto implementations below.
|
||||
use crate::application::{Settings, CryptoLayer};
|
||||
#[cfg(feature = "default")]
|
||||
pub trait CrateCryptoLayer {
|
||||
/// 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();
|
||||
|
||||
/// A user-defined error returned when the `ApplicationLayer` fails to access persistent storage
|
||||
/// for a peer's ratchet states.
|
||||
type StorageError: std::error::Error;
|
||||
|
||||
/// An arbitrary opaque object for use by the application that is attached to each session.
|
||||
type SessionData;
|
||||
}
|
||||
|
||||
use rand_core::OsRng;
|
||||
#[cfg(feature = "default")]
|
||||
impl<Crypto: CrateCryptoLayer> CryptoLayer for Crypto {
|
||||
type Rng = OsRng;
|
||||
type Prp = Aes256Crate;
|
||||
type Aead = AesGcmCrate;
|
||||
type Hash = Sha512Crate;
|
||||
type PublicKey = P384CratePublicKey;
|
||||
type KeyPair = P384CrateKeyPair;
|
||||
type Kem = RustKyber1024PrivateKey;
|
||||
|
||||
type StorageError = Crypto::StorageError;
|
||||
|
||||
type SessionData = Crypto::SessionData;
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::collections::HashMap;
|
||||
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
use crate::application::ApplicationLayer;
|
||||
use crate::application::{CryptoLayer, ApplicationLayer};
|
||||
use crate::crypto::{Aes256Prp, AES_256_KEY_SIZE};
|
||||
use crate::proto::*;
|
||||
use crate::result::{byzantine_fault, ReceiveError};
|
||||
@@ -28,7 +28,7 @@ fn create_fragment_header(
|
||||
}
|
||||
|
||||
/// Corresponds to the fragmentation algorithm described in Section 6.
|
||||
pub fn send_with_fragmentation<App: ApplicationLayer>(
|
||||
pub fn send_with_fragmentation<Crypto: CryptoLayer>(
|
||||
mut send: impl FnMut(Vec<u8>) -> bool,
|
||||
mtu: usize,
|
||||
identifier: u32,
|
||||
@@ -52,7 +52,7 @@ pub fn send_with_fragmentation<App: ApplicationLayer>(
|
||||
fragment.extend(&packet[i..j]);
|
||||
|
||||
if let Some(hk_send) = hk_send {
|
||||
App::Prp::encrypt_in_place(
|
||||
Crypto::Prp::encrypt_in_place(
|
||||
hk_send,
|
||||
(&mut fragment[HEADER_AUTH_START..HEADER_AUTH_END]).try_into().unwrap(),
|
||||
);
|
||||
@@ -95,7 +95,7 @@ impl DefragBuffer {
|
||||
}
|
||||
|
||||
if let Some(hk_recv) = self.hk_recv.as_ref() {
|
||||
App::Prp::decrypt_in_place(
|
||||
<App::Crypto as CryptoLayer>::Prp::decrypt_in_place(
|
||||
hk_recv,
|
||||
(&mut raw_fragment[HEADER_AUTH_START..HEADER_AUTH_END])
|
||||
.try_into()
|
||||
@@ -115,7 +115,7 @@ impl DefragBuffer {
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
let expiration_time = current_time + App::SETTINGS.fragment_assembly_timeout as i64;
|
||||
let expiration_time = current_time + App::Crypto::SETTINGS.fragment_assembly_timeout as i64;
|
||||
let mut map = self.fragment_map.borrow_mut();
|
||||
match map.entry(n) {
|
||||
Entry::Occupied(mut entry) => {
|
||||
@@ -157,7 +157,7 @@ impl DefragBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn service<App: ApplicationLayer>(&self, current_time: i64) {
|
||||
pub fn service(&self, current_time: i64) {
|
||||
let mut map = self.fragment_map.borrow_mut();
|
||||
map.retain(|_, buffer| buffer.expiration_time < current_time);
|
||||
}
|
||||
|
||||
+22
-22
@@ -1,23 +1,23 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::application::ApplicationLayer;
|
||||
use crate::application::CryptoLayer;
|
||||
use crate::Session;
|
||||
|
||||
/// ZSSP events that might be interesting to log or aggregate into metrics.
|
||||
#[allow(missing_docs)]
|
||||
pub enum LogEvent<'a, App: ApplicationLayer> {
|
||||
ResentX1(&'a Arc<Session<App>>),
|
||||
TimeoutX1(&'a Arc<Session<App>>),
|
||||
pub enum LogEvent<'a, Crypto: CryptoLayer> {
|
||||
ResentX1(&'a Arc<Session<Crypto>>),
|
||||
TimeoutX1(&'a Arc<Session<Crypto>>),
|
||||
TimeoutX2,
|
||||
ResentX3(&'a Arc<Session<App>>),
|
||||
TimeoutX3(&'a Arc<Session<App>>),
|
||||
ResentKeyConfirm(&'a Arc<Session<App>>),
|
||||
TimeoutKeyConfirm(&'a Arc<Session<App>>),
|
||||
StartedRekeyingSentK1(&'a Arc<Session<App>>),
|
||||
ResentK1(&'a Arc<Session<App>>),
|
||||
TimeoutK1(&'a Arc<Session<App>>),
|
||||
ResentK2(&'a Arc<Session<App>>),
|
||||
TimeoutK2(&'a Arc<Session<App>>),
|
||||
ResentX3(&'a Arc<Session<Crypto>>),
|
||||
TimeoutX3(&'a Arc<Session<Crypto>>),
|
||||
ResentKeyConfirm(&'a Arc<Session<Crypto>>),
|
||||
TimeoutKeyConfirm(&'a Arc<Session<Crypto>>),
|
||||
StartedRekeyingSentK1(&'a Arc<Session<Crypto>>),
|
||||
ResentK1(&'a Arc<Session<Crypto>>),
|
||||
TimeoutK1(&'a Arc<Session<Crypto>>),
|
||||
ResentK2(&'a Arc<Session<Crypto>>),
|
||||
TimeoutK2(&'a Arc<Session<Crypto>>),
|
||||
/// `(packet_type, packet_counter, fragment_no, fragment_count)`
|
||||
ReceivedRawFragment(u8, u64, usize, usize),
|
||||
ReceivedRawX1,
|
||||
@@ -25,24 +25,24 @@ pub enum LogEvent<'a, App: ApplicationLayer> {
|
||||
X1SucceededChallenge,
|
||||
X1IsAuthSentX2,
|
||||
ReceivedRawChallenge,
|
||||
ChallengeIsAuth(&'a Arc<Session<App>>),
|
||||
ChallengeIsAuth(&'a Arc<Session<Crypto>>),
|
||||
ReceivedRawX2,
|
||||
X2IsAuthSentX3(&'a Arc<Session<App>>),
|
||||
X2IsAuthSentX3(&'a Arc<Session<Crypto>>),
|
||||
ReceivedRawX3,
|
||||
X3IsAuthSentKeyConfirm(&'a Arc<Session<App>>),
|
||||
X3IsAuthSentKeyConfirm(&'a Arc<Session<Crypto>>),
|
||||
ReceivedRawKeyConfirm,
|
||||
KeyConfirmIsAuthSentAck(&'a Arc<Session<App>>),
|
||||
KeyConfirmIsAuthSentAck(&'a Arc<Session<Crypto>>),
|
||||
ReceivedRawAck,
|
||||
AckIsAuth(&'a Arc<Session<App>>),
|
||||
AckIsAuth(&'a Arc<Session<Crypto>>),
|
||||
ReceivedRawK1,
|
||||
K1IsAuthSentK2(&'a Arc<Session<App>>),
|
||||
K1IsAuthSentK2(&'a Arc<Session<Crypto>>),
|
||||
ReceivedRawK2,
|
||||
K2IsAuthSentKeyConfirm(&'a Arc<Session<App>>),
|
||||
K2IsAuthSentKeyConfirm(&'a Arc<Session<Crypto>>),
|
||||
ReceivedRawD,
|
||||
DIsAuthClosedSession(&'a Arc<Session<App>>),
|
||||
DIsAuthClosedSession(&'a Arc<Session<Crypto>>),
|
||||
}
|
||||
|
||||
impl<'a, App: ApplicationLayer> std::fmt::Debug for LogEvent<'a, App> {
|
||||
impl<'a, Crypto: CryptoLayer> std::fmt::Debug for LogEvent<'a, Crypto> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::ResentX1(_) => f.debug_tuple("ResentX1").finish(),
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::application::ApplicationLayer;
|
||||
use crate::application::CryptoLayer;
|
||||
use crate::Session;
|
||||
|
||||
/// An error that can occur when attempting to open a session.
|
||||
@@ -121,13 +121,13 @@ pub(crate) use byzantine_fault;
|
||||
|
||||
/// Result generated by the context packet receive function, with possible payloads.
|
||||
#[derive(Clone)]
|
||||
pub enum ReceiveOk<App: ApplicationLayer> {
|
||||
pub enum ReceiveOk<Crypto: CryptoLayer> {
|
||||
/// Packet superficially appeared valid but is not associated with a session yet.
|
||||
/// This can occur because the packet was only a fragment of a larger packet,
|
||||
/// or if it was a control packet that does not go through full Noise authentication.
|
||||
Unassociated,
|
||||
/// Packet was authentic and belongs to this specific session.
|
||||
Session(Arc<Session<App>>, SessionEvent),
|
||||
Session(Arc<Session<Crypto>>, SessionEvent),
|
||||
}
|
||||
/// Something that can occur to an associated session when a packet is received successfully,
|
||||
/// including receiving a payload of decrypted, authenticated data.
|
||||
|
||||
+14
-14
@@ -2,19 +2,19 @@ use std::marker::PhantomData;
|
||||
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
use crate::application::ApplicationLayer;
|
||||
use crate::application::CryptoLayer;
|
||||
use crate::crypto::*;
|
||||
use crate::proto::*;
|
||||
|
||||
pub struct SymmetricState<App: ApplicationLayer> {
|
||||
pub struct SymmetricState<Crypto: CryptoLayer> {
|
||||
k: Zeroizing<[u8; AES_256_KEY_SIZE]>,
|
||||
ck: Zeroizing<[u8; HASHLEN]>,
|
||||
h: [u8; HASHLEN],
|
||||
/// If anyone knows a better way to get rid of the "parameter `App` is never used" error please
|
||||
/// let me know.
|
||||
_app: PhantomData<fn() -> App::SessionData>,
|
||||
/// If anyone knows a better way to get rid of the "parameter `Crypto` is never used" error
|
||||
/// please let me know.
|
||||
_app: PhantomData<fn() -> Crypto::SessionData>,
|
||||
}
|
||||
impl<App: ApplicationLayer> Clone for SymmetricState<App> {
|
||||
impl<Crypto: CryptoLayer> Clone for SymmetricState<Crypto> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
k: self.k.clone(),
|
||||
@@ -25,7 +25,7 @@ impl<App: ApplicationLayer> Clone for SymmetricState<App> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<App: ApplicationLayer> SymmetricState<App> {
|
||||
impl<Crypto: CryptoLayer> SymmetricState<Crypto> {
|
||||
/// HMAC-SHA512 key derivation based on KBKDF Counter Mode:
|
||||
/// https://csrc.nist.gov/publications/detail/sp/800-108/rev-1/final.
|
||||
/// Cryptographically this isn't meaningfully different from
|
||||
@@ -55,18 +55,18 @@ impl<App: ApplicationLayer> SymmetricState<App> {
|
||||
buffer.extend(&(num_outputs * 8 * HASHLEN as u16).to_be_bytes());
|
||||
|
||||
debug_assert!(num_outputs >= 1);
|
||||
*output1 = App::Hash::hmac(input_key_material, &buffer);
|
||||
*output1 = Crypto::Hash::hmac(input_key_material, &buffer);
|
||||
|
||||
if let Some(output2) = output2 {
|
||||
debug_assert!(num_outputs >= 2);
|
||||
buffer[0] = 2;
|
||||
*output2 = App::Hash::hmac(input_key_material, &buffer);
|
||||
*output2 = Crypto::Hash::hmac(input_key_material, &buffer);
|
||||
}
|
||||
|
||||
if let Some(output3) = output3 {
|
||||
debug_assert!(num_outputs >= 3);
|
||||
buffer[0] = 3;
|
||||
*output3 = App::Hash::hmac(input_key_material, &buffer);
|
||||
*output3 = Crypto::Hash::hmac(input_key_material, &buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ impl<App: ApplicationLayer> SymmetricState<App> {
|
||||
}
|
||||
/// Corresponds to Noise `MixHash`.
|
||||
pub fn mix_hash(&mut self, data: &[u8]) {
|
||||
let mut hash = App::Hash::new();
|
||||
let mut hash = Crypto::Hash::new();
|
||||
hash.update(&self.h);
|
||||
hash.update(data);
|
||||
self.h = hash.finish();
|
||||
@@ -129,7 +129,7 @@ impl<App: ApplicationLayer> SymmetricState<App> {
|
||||
plaintext_start: usize,
|
||||
buffer: &mut Vec<u8>,
|
||||
) {
|
||||
let tag = App::Aead::encrypt_in_place(&self.k, &iv, Some(&self.h), &mut buffer[plaintext_start..]);
|
||||
let tag = Crypto::Aead::encrypt_in_place(&self.k, &iv, Some(&self.h), &mut buffer[plaintext_start..]);
|
||||
buffer.extend(&tag);
|
||||
self.mix_hash(&buffer[plaintext_start..]);
|
||||
}
|
||||
@@ -141,11 +141,11 @@ impl<App: ApplicationLayer> SymmetricState<App> {
|
||||
buffer: &mut [u8],
|
||||
tag: [u8; AES_GCM_TAG_SIZE],
|
||||
) -> bool {
|
||||
let mut hash = App::Hash::new();
|
||||
let mut hash = Crypto::Hash::new();
|
||||
hash.update(&self.h);
|
||||
hash.update(buffer);
|
||||
hash.update(&tag);
|
||||
let ret = App::Aead::decrypt_in_place(&self.k, &iv, Some(&self.h), buffer, &tag);
|
||||
let ret = Crypto::Aead::decrypt_in_place(&self.k, &iv, Some(&self.h), buffer, &tag);
|
||||
self.h = hash.finish();
|
||||
ret
|
||||
}
|
||||
|
||||
+114
-114
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user