diff --git a/Cargo.lock b/Cargo.lock index 5f2adbf995..d0c007c505 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5124,6 +5124,7 @@ dependencies = [ name = "sn_networking" version = "0.12.39" dependencies = [ + "aes-gcm", "async-trait", "backoff", "blsttc", diff --git a/sn_networking/Cargo.toml b/sn_networking/Cargo.toml index 4751e973f4..9426648b1c 100644 --- a/sn_networking/Cargo.toml +++ b/sn_networking/Cargo.toml @@ -39,6 +39,7 @@ tokio = { version = "1.32.0", features = ["io-util", "macros", "rt", "sync", "ti tracing = { version = "~0.1.26" } xor_name = "5.0.0" backoff = { version = "0.4.0", features = ["tokio"] } +aes-gcm = "0.10.3" [dev-dependencies] bls = { package = "blsttc", version = "8.0.1" } diff --git a/sn_networking/src/record_store.rs b/sn_networking/src/record_store.rs index 0360d4bc2b..a4740f226e 100644 --- a/sn_networking/src/record_store.rs +++ b/sn_networking/src/record_store.rs @@ -9,6 +9,11 @@ use crate::target_arch::{spawn, Instant}; use crate::{cmd::SwarmCmd, event::NetworkEvent, send_swarm_cmd}; +use aes_gcm::aead::generic_array::GenericArray; +use aes_gcm::{ + aead::{Aead, AeadCore, KeyInit, OsRng}, + Aes256Gcm, +}; use libp2p::{ identity::PeerId, kad::{ @@ -56,6 +61,11 @@ pub struct NodeRecordStore { record_count_metric: Option, /// Counting how many times got paid received_payment_count: usize, + /// Encyption key for the records, randomly generated at node startup + encryption_details: ( + Aes256Gcm, + GenericArray::NonceSize>, + ), } /// Configuration for a `DiskBackedRecordStore`. @@ -87,6 +97,10 @@ impl NodeRecordStore { network_event_sender: mpsc::Sender, swarm_cmd_sender: mpsc::Sender, ) -> Self { + let key = Aes256Gcm::generate_key(&mut OsRng); + let cipher = Aes256Gcm::new(&key); + let nonce = Aes256Gcm::generate_nonce(&mut OsRng); + NodeRecordStore { local_key: KBucketKey::from(local_id), config, @@ -97,6 +111,7 @@ impl NodeRecordStore { #[cfg(feature = "open-metrics")] record_count_metric: None, received_payment_count: 0, + encryption_details: (cipher, nonce), } } @@ -117,26 +132,41 @@ impl NodeRecordStore { hex_string } - fn read_from_disk<'a>(key: &Key, storage_dir: &Path) -> Option> { + fn read_from_disk<'a>( + encryption_details: &( + Aes256Gcm, + GenericArray::NonceSize>, + ), + key: &Key, + storage_dir: &Path, + ) -> Option> { + let (cipher, nonce) = encryption_details; + let start = Instant::now(); let filename = Self::key_to_hex(key); let file_path = storage_dir.join(&filename); // we should only be reading if we know the record is written to disk properly match fs::read(file_path) { - Ok(value) => { + Ok(ciphertext) => { // vdash metric (if modified please notify at https://github.com/happybeing/vdash/issues): info!( "Retrieved record from disk! filename: {filename} after {:?}", start.elapsed() ); - let record = Record { - key: key.clone(), - value, - publisher: None, - expires: None, - }; - Some(Cow::Owned(record)) + + if let Ok(value) = cipher.decrypt(nonce, ciphertext.as_ref()) { + let record = Record { + key: key.clone(), + value, + publisher: None, + expires: None, + }; + return Some(Cow::Owned(record)); + } else { + error!("Error while decrypting record. filename: {filename}"); + None + } } Err(err) => { error!("Error while reading file. filename: {filename}, error: {err:?}"); @@ -252,27 +282,33 @@ impl NodeRecordStore { let _ = metric.set(self.records.len() as i64); } + let (cipher, nonce) = self.encryption_details.clone(); + let cloned_cmd_sender = self.swarm_cmd_sender.clone(); spawn(async move { - let cmd = match fs::write(&file_path, r.value) { - Ok(_) => { - // vdash metric (if modified please notify at https://github.com/happybeing/vdash/issues): - info!("Wrote record {record_key:?} to disk! filename: {filename}"); - - SwarmCmd::AddLocalRecordAsStored { - key: r.key, - record_type, + if let Ok(value) = cipher.encrypt(&nonce, r.value.as_ref()) { + let cmd = match fs::write(&file_path, value) { + Ok(_) => { + // vdash metric (if modified please notify at https://github.com/happybeing/vdash/issues): + info!("Wrote record {record_key:?} to disk! filename: {filename}"); + + SwarmCmd::AddLocalRecordAsStored { + key: r.key, + record_type, + } } - } - Err(err) => { - error!( + Err(err) => { + error!( "Error writing record {record_key:?} filename: {filename}, error: {err:?}" ); - SwarmCmd::RemoveFailedLocalRecord { key: r.key } - } - }; + SwarmCmd::RemoveFailedLocalRecord { key: r.key } + } + }; - send_swarm_cmd(cloned_cmd_sender, cmd); + send_swarm_cmd(cloned_cmd_sender, cmd); + } else { + warn!("Failed to encrypt record {record_key:?} filename: {filename}"); + } }); Ok(()) @@ -341,7 +377,7 @@ impl RecordStore for NodeRecordStore { debug!("GET request for Record key: {key}"); - Self::read_from_disk(k, &self.config.storage_dir) + Self::read_from_disk(&self.encryption_details, k, &self.config.storage_dir) } fn put(&mut self, record: Record) -> Result<()> { @@ -732,8 +768,12 @@ mod tests { // Confirm the pruned_key got removed, looping to allow async disk ops to complete. let mut iteration = 0; while iteration < max_iterations { - if NodeRecordStore::read_from_disk(&pruned_key, &store_config.storage_dir) - .is_none() + if NodeRecordStore::read_from_disk( + &store.encryption_details, + &pruned_key, + &store_config.storage_dir, + ) + .is_none() { break; }