From b5def42c7058b959cf3e0d1bcebf7ff71369931f Mon Sep 17 00:00:00 2001 From: Daniel Hugenroth Date: Sat, 24 Sep 2022 17:24:55 +0100 Subject: [PATCH 1/2] Allow for RNG propagation --- Cargo.toml | 2 +- src/crypto/keys.rs | 9 ++-- src/header/delays.rs | 12 +++++- src/header/mod.rs | 15 ++++++- src/header/routing/destination.rs | 21 ++++++---- src/header/routing/mod.rs | 21 ++++++++-- src/packet/builder.rs | 25 ++++++++++- src/surb/mod.rs | 70 ++++++++++++++++++++++++++----- 8 files changed, 146 insertions(+), 29 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 98a305e..617e445 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,10 +24,10 @@ blake2 = "0.8.0" # cannot be updated due to outdated dependency inside lioness byteorder = "1.3.2" subtle = "2.3.0" - [dev-dependencies] mockall = "0.10.2" criterion = "0.3" +rand_chacha = "0.2.2" [[bench]] name = "benchmarks" diff --git a/src/crypto/keys.rs b/src/crypto/keys.rs index 3575200..f07f41d 100644 --- a/src/crypto/keys.rs +++ b/src/crypto/keys.rs @@ -66,8 +66,7 @@ impl PrivateKey { // honestly, this method shouldn't really exist, but right now we have no decent // rng propagation in the library pub fn new() -> Self { - let mut rng = OsRng; - Self::new_with_rng(&mut rng) + Self::new_with_rng(&mut OsRng) } pub fn new_with_rng(rng: &mut R) -> Self { @@ -145,7 +144,11 @@ impl PartialEq for PublicKey { impl Eq for PublicKey {} pub fn keygen() -> (PrivateKey, PublicKey) { - let private_key = PrivateKey::new(); + keygen_with_rng(&mut OsRng) +} + +pub fn keygen_with_rng(rng: &mut R) -> (PrivateKey, PublicKey) { + let private_key = PrivateKey::new_with_rng(rng); let public_key = PublicKey::from(&private_key); (private_key, public_key) } diff --git a/src/header/delays.rs b/src/header/delays.rs index 4f87d05..4cc9b83 100644 --- a/src/header/delays.rs +++ b/src/header/delays.rs @@ -14,6 +14,8 @@ use crate::constants::DELAY_LENGTH; use byteorder::{BigEndian, ByteOrder}; +use rand::rngs::OsRng; +use rand::{CryptoRng, RngCore}; use rand_distr::{Distribution, Exp}; use std::{borrow::Borrow, time::Duration}; @@ -105,11 +107,19 @@ pub fn generate_from_nanos(number: usize, average_delay: u64) -> Vec { } pub fn generate_from_average_duration(number: usize, average_delay: Duration) -> Vec { + generate_from_average_duration_with_rng(number, average_delay, &mut OsRng) +} + +pub fn generate_from_average_duration_with_rng( + number: usize, + average_delay: Duration, + rng: &mut R, +) -> Vec { let exp = Exp::new(1.0 / average_delay.as_nanos() as f64).unwrap(); std::iter::repeat(()) .take(number) - .map(|_| Delay::new_from_nanos(exp.sample(&mut rand::thread_rng()).round() as u64)) + .map(|_| Delay::new_from_nanos(exp.sample(rng).round() as u64)) .collect() } diff --git a/src/header/mod.rs b/src/header/mod.rs index 009c175..92afb46 100644 --- a/src/header/mod.rs +++ b/src/header/mod.rs @@ -24,6 +24,8 @@ use crate::{Error, ErrorKind, Result}; use crypto::{EphemeralSecret, PrivateKey, SharedSecret}; use curve25519_dalek::scalar::Scalar; use keys::RoutingKeys; +use rand::rngs::OsRng; +use rand::{CryptoRng, RngCore}; pub mod delays; pub mod filler; @@ -54,15 +56,26 @@ impl SphinxHeader { route: &[Node], delays: &[Delay], destination: &Destination, + ) -> (Self, Vec) { + Self::new_with_rng(initial_secret, route, delays, destination, &mut OsRng) + } + + pub fn new_with_rng( + initial_secret: &EphemeralSecret, + route: &[Node], + delays: &[Delay], + destination: &Destination, + rng: &mut R, ) -> (Self, Vec) { let key_material = keys::KeyMaterial::derive(route, initial_secret); let filler_string = Filler::new(&key_material.routing_keys[..route.len() - 1]); - let routing_info = routing::EncapsulatedRoutingInformation::new( + let routing_info = routing::EncapsulatedRoutingInformation::new_with_rng( route, destination, delays, &key_material.routing_keys, filler_string, + rng, ); // encapsulate header.routing information, compute MACs diff --git a/src/header/routing/destination.rs b/src/header/routing/destination.rs index a1cfd6d..c6023c6 100644 --- a/src/header/routing/destination.rs +++ b/src/header/routing/destination.rs @@ -23,7 +23,7 @@ use crate::header::routing::nodes::EncryptedRoutingInformation; use crate::header::routing::{RoutingFlag, Version, ENCRYPTED_ROUTING_INFO_SIZE, FINAL_HOP}; use crate::route::{Destination, DestinationAddressBytes, SURBIdentifier}; use crate::utils; -use rand::rngs::OsRng; +use rand::{CryptoRng, RngCore}; // this is going through the following transformations: /* @@ -63,11 +63,15 @@ impl FinalRoutingInformation { ENCRYPTED_ROUTING_INFO_SIZE - (FILLER_STEP_SIZE_INCREASE * (route_len - 1)) } - pub(super) fn add_padding(self, route_len: usize) -> PaddedFinalRoutingInformation { + pub(super) fn add_padding( + self, + route_len: usize, + rng: &mut R, + ) -> PaddedFinalRoutingInformation { // paper uses 0 bytes for this, however, we use random instead so that we would not be affected by the // attack on sphinx described by Kuhn et al. let padding = utils::bytes::random( - &mut OsRng, + rng, ENCRYPTED_ROUTING_INFO_SIZE - (FILLER_STEP_SIZE_INCREASE * (route_len - 1)) - FINAL_NODE_META_INFO_LENGTH, @@ -157,6 +161,7 @@ mod test_encapsulating_final_routing_information_and_mac { random_node, }, }; + use rand::rngs::OsRng; #[test] fn it_returns_mac_on_correct_data() { @@ -174,6 +179,7 @@ mod test_encapsulating_final_routing_information_and_mac { &routing_keys.last().unwrap(), filler, route.len(), + &mut OsRng, ); let expected_mac = HeaderIntegrityMac::compute( @@ -191,6 +197,7 @@ mod test_encapsulating_final_routing_information_and_mac { mod test_encapsulating_final_routing_information { use super::*; use crate::test_utils::fixtures::{destination_fixture, filler_fixture, routing_keys_fixture}; + use rand::rngs::OsRng; #[test] fn it_produces_result_of_length_filler_plus_padded_concatenated_destination_and_identifier_and_flag_for_route_of_length_5( @@ -201,7 +208,7 @@ mod test_encapsulating_final_routing_information { let destination = destination_fixture(); let final_routing_header = FinalRoutingInformation::new(&destination, route_len) - .add_padding(route_len) + .add_padding(route_len, &mut OsRng) .encrypt(final_keys.stream_cipher_key, route_len) .combine_with_filler(filler, route_len); @@ -222,7 +229,7 @@ mod test_encapsulating_final_routing_information { let destination = destination_fixture(); let final_routing_header = FinalRoutingInformation::new(&destination, route_len) - .add_padding(route_len) + .add_padding(route_len, &mut OsRng) .encrypt(final_keys.stream_cipher_key, route_len) .combine_with_filler(filler, route_len); @@ -243,7 +250,7 @@ mod test_encapsulating_final_routing_information { let destination = destination_fixture(); let final_routing_header = FinalRoutingInformation::new(&destination, route_len) - .add_padding(route_len) + .add_padding(route_len, &mut OsRng) .encrypt(final_keys.stream_cipher_key, route_len) .combine_with_filler(filler, route_len); @@ -264,7 +271,7 @@ mod test_encapsulating_final_routing_information { let destination = destination_fixture(); FinalRoutingInformation::new(&destination, route_len) - .add_padding(route_len) + .add_padding(route_len, &mut OsRng) .encrypt(final_keys.stream_cipher_key, route_len) .combine_with_filler(filler, route_len); } diff --git a/src/header/routing/mod.rs b/src/header/routing/mod.rs index c3dcd8a..dfcd7de 100644 --- a/src/header/routing/mod.rs +++ b/src/header/routing/mod.rs @@ -21,6 +21,8 @@ use crate::header::routing::destination::FinalRoutingInformation; use crate::header::routing::nodes::{EncryptedRoutingInformation, RoutingInformation}; use crate::route::{Destination, Node, NodeAddressBytes}; use crate::{Error, ErrorKind, Result}; +use rand::rngs::OsRng; +use rand::{CryptoRng, RngCore}; pub const TRUNCATED_ROUTING_INFO_SIZE: usize = ENCRYPTED_ROUTING_INFO_SIZE - (NODE_META_INFO_SIZE + HEADER_INTEGRITY_MAC_SIZE); @@ -80,6 +82,17 @@ impl EncapsulatedRoutingInformation { delays: &[Delay], routing_keys: &[RoutingKeys], filler: Filler, + ) -> Self { + Self::new_with_rng(route, destination, delays, routing_keys, filler, &mut OsRng) + } + + pub fn new_with_rng( + route: &[Node], + destination: &Destination, + delays: &[Delay], + routing_keys: &[RoutingKeys], + filler: Filler, + rng: &mut R, ) -> Self { assert_eq!(route.len(), routing_keys.len()); assert_eq!(delays.len(), route.len()); @@ -90,7 +103,7 @@ impl EncapsulatedRoutingInformation { }; let encapsulated_destination_routing_info = - Self::for_final_hop(destination, final_keys, filler, route.len()); + Self::for_final_hop(destination, final_keys, filler, route.len(), rng); Self::for_forward_hops( encapsulated_destination_routing_info, @@ -100,15 +113,16 @@ impl EncapsulatedRoutingInformation { ) } - fn for_final_hop( + fn for_final_hop( dest: &Destination, routing_keys: &RoutingKeys, filler: Filler, route_len: usize, + rng: &mut R, ) -> Self { // personal note: I like how this looks so much. FinalRoutingInformation::new(dest, route_len) - .add_padding(route_len) // add padding to obtain correct destination length + .add_padding(route_len, rng) // add padding to obtain correct destination length .encrypt(routing_keys.stream_cipher_key, route_len) // encrypt with the key of final node (in our case service provider) .combine_with_filler(filler, route_len) // add filler to get header of correct length .encapsulate_with_mac(routing_keys.header_integrity_hmac_key) // combine the previous data with a MAC on the header (also calculated with the SPs key) @@ -304,6 +318,7 @@ mod encapsulating_forward_routing_information { &routing_keys.last().unwrap(), filler, route.len(), + &mut OsRng, ); let destination_routing_info_copy = destination_routing_info.clone(); diff --git a/src/packet/builder.rs b/src/packet/builder.rs index 25c7cf8..0a8000a 100644 --- a/src/packet/builder.rs +++ b/src/packet/builder.rs @@ -5,6 +5,8 @@ use crate::{ route::{Destination, Node}, Result, SphinxPacket, }; +use rand::rngs::OsRng; +use rand::{CryptoRng, RngCore}; pub const DEFAULT_PAYLOAD_SIZE: usize = 1024; @@ -34,10 +36,29 @@ impl<'a> SphinxPacketBuilder<'a> { route: &[Node], destination: &Destination, delays: &[Delay], + ) -> Result { + self.build_packet_with_rng(message, route, destination, delays, &mut OsRng) + } + + pub fn build_packet_with_rng( + &self, + message: Vec, + route: &[Node], + destination: &Destination, + delays: &[Delay], + rng: &mut R, ) -> Result { let (header, payload_keys) = match self.initial_secret.as_ref() { - Some(initial_secret) => SphinxHeader::new(initial_secret, route, delays, destination), - None => SphinxHeader::new(&EphemeralSecret::new(), route, delays, destination), + Some(initial_secret) => { + SphinxHeader::new_with_rng(initial_secret, route, delays, destination, rng) + } + None => SphinxHeader::new_with_rng( + &EphemeralSecret::new_with_rng(rng), + route, + delays, + destination, + rng, + ), }; // no need to check if plaintext has correct length as this check is already performed in payload encapsulation diff --git a/src/surb/mod.rs b/src/surb/mod.rs index 7a88f57..b62f1d6 100644 --- a/src/surb/mod.rs +++ b/src/surb/mod.rs @@ -6,6 +6,8 @@ use crate::route::{Destination, Node, NodeAddressBytes}; use crate::{crypto::EphemeralSecret, Error, ErrorKind, Result}; use crate::{header, SphinxPacket}; use header::{SphinxHeader, HEADER_SIZE}; +use rand::rngs::OsRng; +use rand::{CryptoRng, RngCore}; use std::fmt; /// A Single Use Reply Block (SURB) must have a pre-aggregated Sphinx header, @@ -13,7 +15,7 @@ use std::fmt; /// used to layer encrypt the payload. #[allow(non_snake_case)] pub struct SURB { - SURB_header: header::SphinxHeader, + SURB_header: SphinxHeader, first_hop_address: NodeAddressBytes, payload_keys: Vec, } @@ -51,14 +53,27 @@ impl SURBMaterial { #[allow(non_snake_case)] pub fn construct_SURB(self) -> Result { - let surb_initial_secret = EphemeralSecret::new(); - SURB::new(surb_initial_secret, self) + Self::construct_SURB_with_rng(self, &mut OsRng) + } + + #[allow(non_snake_case)] + pub fn construct_SURB_with_rng(self, rng: &mut R) -> Result { + let surb_initial_secret = EphemeralSecret::new_with_rng(rng); + SURB::new_with_rng(surb_initial_secret, self, rng) } } #[allow(non_snake_case)] impl SURB { pub fn new(surb_initial_secret: EphemeralSecret, surb_material: SURBMaterial) -> Result { + Self::new_with_rng(surb_initial_secret, surb_material, &mut OsRng) + } + + pub fn new_with_rng( + surb_initial_secret: EphemeralSecret, + surb_material: SURBMaterial, + rng: &mut R, + ) -> Result { let surb_route = surb_material.surb_route; let surb_delays = surb_material.surb_delays; let surb_destination = surb_material.surb_destination; @@ -78,11 +93,12 @@ impl SURB { let first_hop = surb_route.first().unwrap(); - let (header, payload_keys) = header::SphinxHeader::new( + let (header, payload_keys) = SphinxHeader::new_with_rng( &surb_initial_secret, &surb_route, &surb_delays, &surb_destination, + rng, ); Ok(SURB { @@ -168,21 +184,27 @@ mod prepare_and_use_process_surb { use crate::crypto; use crate::header::{delays, HEADER_SIZE}; use crate::{packet::builder::DEFAULT_PAYLOAD_SIZE, test_utils::fixtures::destination_fixture}; + use rand_chacha::rand_core::{RngCore, SeedableRng}; use std::time::Duration; #[allow(non_snake_case)] fn SURB_fixture() -> SURB { - let (_, node1_pk) = crypto::keygen(); + SURB_fixture_with_rng(&mut OsRng) + } + + #[allow(non_snake_case)] + fn SURB_fixture_with_rng(rng: &mut R) -> SURB { + let (_, node1_pk) = crypto::keygen_with_rng(rng); let node1 = Node { address: NodeAddressBytes::from_bytes([5u8; NODE_ADDRESS_LENGTH]), pub_key: node1_pk, }; - let (_, node2_pk) = crypto::keygen(); + let (_, node2_pk) = crypto::keygen_with_rng(rng); let node2 = Node { address: NodeAddressBytes::from_bytes([4u8; NODE_ADDRESS_LENGTH]), pub_key: node2_pk, }; - let (_, node3_pk) = crypto::keygen(); + let (_, node3_pk) = crypto::keygen_with_rng(rng); let node3 = Node { address: NodeAddressBytes::from_bytes([2u8; NODE_ADDRESS_LENGTH]), pub_key: node3_pk, @@ -190,13 +212,17 @@ mod prepare_and_use_process_surb { let surb_route = vec![node1, node2, node3]; let surb_destination = destination_fixture(); - let surb_initial_secret = EphemeralSecret::new(); - let surb_delays = - delays::generate_from_average_duration(surb_route.len(), Duration::from_secs(3)); + let surb_initial_secret = EphemeralSecret::new_with_rng(rng); + let surb_delays = delays::generate_from_average_duration_with_rng( + surb_route.len(), + Duration::from_secs(3), + rng, + ); - SURB::new( + SURB::new_with_rng( surb_initial_secret, SURBMaterial::new(surb_route, surb_delays, surb_destination), + rng, ) .unwrap() } @@ -277,4 +303,26 @@ mod prepare_and_use_process_surb { dummy_SURB.SURB_header.to_bytes() ); } + + #[test] + fn when_same_rng_then_surbs_identical() { + let mut rng1 = rand_chacha::ChaCha20Rng::seed_from_u64(0); + let mut rng2 = rand_chacha::ChaCha20Rng::seed_from_u64(0); + + let surb1 = SURB_fixture_with_rng(&mut rng1); + let surb2 = SURB_fixture_with_rng(&mut rng2); + + assert_eq!(surb1.to_bytes(), surb2.to_bytes()); + } + + #[test] + fn when_different_rng_then_surbs_different() { + let mut rng1 = rand_chacha::ChaCha20Rng::seed_from_u64(0); + let mut rng2 = rand_chacha::ChaCha20Rng::seed_from_u64(1); + + let surb1 = SURB_fixture_with_rng(&mut rng1); + let surb2 = SURB_fixture_with_rng(&mut rng2); + + assert_ne!(surb1.to_bytes(), surb2.to_bytes()); + } } From 8e4a7a906a5cbc86ceffe7168afd2d6746d7bb61 Mon Sep 17 00:00:00 2001 From: Daniel Hugenroth Date: Fri, 21 Jul 2023 22:19:12 +0100 Subject: [PATCH 2/2] Rebase and fix message type --- src/packet/builder.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/packet/builder.rs b/src/packet/builder.rs index 513b0d0..0a6eb16 100644 --- a/src/packet/builder.rs +++ b/src/packet/builder.rs @@ -40,9 +40,9 @@ impl<'a> SphinxPacketBuilder<'a> { self.build_packet_with_rng(message, route, destination, delays, &mut OsRng) } - pub fn build_packet_with_rng( + pub fn build_packet_with_rng>( &self, - message: Vec, + message: M, route: &[Node], destination: &Destination, delays: &[Delay],