From e21312ba5466b3c5367dfd1eafd8d598f9a419f7 Mon Sep 17 00:00:00 2001 From: Gabriel Viganotti Date: Thu, 14 Mar 2024 17:07:04 -0300 Subject: [PATCH] feat(folders): Folders and AccPacket to accept an encryption key for metadata chunks --- sn_cli/src/subcommands/acc_packet.rs | 43 +++++++++++++++++++--------- sn_client/src/folders.rs | 42 ++++++++++++++++++++++----- 2 files changed, 65 insertions(+), 20 deletions(-) diff --git a/sn_cli/src/subcommands/acc_packet.rs b/sn_cli/src/subcommands/acc_packet.rs index bf24ed3913..65e0c815db 100644 --- a/sn_cli/src/subcommands/acc_packet.rs +++ b/sn_cli/src/subcommands/acc_packet.rs @@ -8,6 +8,7 @@ mod change_tracking; +use bls::PublicKey; use change_tracking::*; use super::files::ChunkManager; @@ -58,6 +59,8 @@ pub struct AccountPacket { impl AccountPacket { /// Initialise directory as a fresh new packet with the given/random address for its root Folder. + // TODO: receive a SK which will be used for encrypting the files/folders metadata chunks. Currently, + // when the user requests to encrypt data, the provided Client's signer key is used. pub fn init( client: Client, wallet_dir: &Path, @@ -180,7 +183,7 @@ impl AccountPacket { /// both pushing and pulling changes to/form the network. pub async fn sync(&mut self, options: FilesUploadOptions) -> Result<()> { let ChangesToApply { folders, mutations } = - self.scan_files_and_folders_for_changes(false)?; + self.scan_files_and_folders_for_changes(options.make_data_public)?; if mutations.is_empty() { println!("No local changes made to files/folders to be pushed to network."); @@ -368,16 +371,20 @@ impl AccountPacket { // Scan existing files and folders on disk, generating a report of all the detected // changes based on the tracking info kept locally. - // TODO: encrypt the data-map and metadata if make_data_public is false. - fn scan_files_and_folders_for_changes( - &self, - _make_data_public: bool, - ) -> Result { + // If make_data_public is false the metadata chunks are encrypted. + fn scan_files_and_folders_for_changes(&self, make_data_public: bool) -> Result { // we don't use the local cache in order to realise of any changes made to files content. let mut chunk_manager = ChunkManager::new(&self.tracking_info_dir); chunk_manager.chunk_with_iter(self.iter_only_files(), false, false)?; - let mut changes = self.read_folders_hierarchy_from_disk()?; + // encrypt the metadata chunk if make_data_public is false. + let encryption_pk = if make_data_public { + None + } else { + Some(self.client.signer_pk()) + }; + + let mut changes = self.read_folders_hierarchy_from_disk(encryption_pk)?; // add chunked files to the corresponding Folders let folders = &mut changes.folders; @@ -392,13 +399,14 @@ impl AccountPacket { Ok(Some(tracking_info)) => match &tracking_info.metadata.content { FolderEntry::File(chunk) => { if chunk.address() != &chunked_file.head_chunk_address { - // TODO: we need to encrypt the data-map and metadata if make_data_public is false. let (entry_hash, meta_xorname, metadata) = replace_item_in_folder( &mut parent_folder, tracking_info.entry_hash, chunked_file.file_name.clone(), chunked_file.data_map.clone(), + encryption_pk, )?; + changes.mutations.push(Mutation::FileContentChanged(( tracking_info.meta_xorname, MetadataTrackingInfo { @@ -412,12 +420,12 @@ impl AccountPacket { } FolderEntry::Folder(_) => { // New file found where there used to be a folder - // TODO: we need to encrypt the data-map and metadata if make_data_public is false. let (entry_hash, meta_xorname, metadata) = replace_item_in_folder( &mut parent_folder, tracking_info.entry_hash, chunked_file.file_name.clone(), chunked_file.data_map.clone(), + encryption_pk, )?; changes .mutations @@ -430,11 +438,11 @@ impl AccountPacket { } }, Ok(None) => { - // TODO: we need to encrypt the data-map and metadata if make_data_public is false. let (entry_hash, meta_xorname, metadata) = parent_folder.get_mut().0.add_file( chunked_file.file_name.clone(), chunked_file.data_map.clone(), + encryption_pk, )?; parent_folder.get_mut().1.has_new_entries(); @@ -486,7 +494,10 @@ impl AccountPacket { } // Build Folders hierarchy from the set files dir - fn read_folders_hierarchy_from_disk(&self) -> Result { + fn read_folders_hierarchy_from_disk( + &self, + encryption_pk: Option, + ) -> Result { let mut changes = ChangesToApply::default(); for (dir_path, depth, parent, dir_name) in self.iter_only_dirs().filter_map(|entry| { entry.path().parent().map(|parent| { @@ -513,7 +524,7 @@ impl AccountPacket { if folder_change.is_new_folder() { let (entry_hash, meta_xorname, metadata) = - parent_folder.add_folder(dir_name, curr_folder_addr)?; + parent_folder.add_folder(dir_name, curr_folder_addr, encryption_pk)?; parent_folder_change.has_new_entries(); changes @@ -786,10 +797,16 @@ fn replace_item_in_folder( entry_hash: EntryHash, file_name: OsString, data_map: Chunk, + encryption_pk: Option, ) -> Result<(EntryHash, XorName, Metadata)> { let (ref mut folders_api, ref mut folder_change) = folder.get_mut(); folder_change.has_new_entries(); - let res = folders_api.replace_file(entry_hash, file_name.clone(), data_map.clone())?; + let res = folders_api.replace_file( + entry_hash, + file_name.clone(), + data_map.clone(), + encryption_pk, + )?; Ok(res) } diff --git a/sn_client/src/folders.rs b/sn_client/src/folders.rs index a683aab75e..0b9aaaaa1e 100644 --- a/sn_client/src/folders.rs +++ b/sn_client/src/folders.rs @@ -10,6 +10,7 @@ use crate::FilesApi; use super::{error::Result, Client, ClientRegister, WalletClient}; +use bls::{Ciphertext, PublicKey}; use self_encryption::MAX_CHUNK_SIZE; use sn_protocol::{ storage::{Chunk, ChunkAddress, RegisterAddress, RetryStrategy}, @@ -106,6 +107,7 @@ impl FoldersApi { &mut self, file_name: OsString, data_map_chunk: Chunk, + encryption_pk: Option, ) -> Result<(EntryHash, XorName, Metadata)> { // create metadata Chunk for this entry let metadata = Metadata { @@ -113,7 +115,7 @@ impl FoldersApi { content: FolderEntry::File(data_map_chunk), }; - self.add_entry(metadata, &BTreeSet::default()) + self.add_entry(metadata, &BTreeSet::default(), encryption_pk) } /// Add subfolder as entry of this Folder (locally). @@ -121,6 +123,7 @@ impl FoldersApi { &mut self, folder_name: OsString, address: RegisterAddress, + encryption_pk: Option, ) -> Result<(EntryHash, XorName, Metadata)> { // create metadata Chunk for this entry let metadata = Metadata { @@ -128,7 +131,7 @@ impl FoldersApi { content: FolderEntry::Folder(address), }; - self.add_entry(metadata, &BTreeSet::default()) + self.add_entry(metadata, &BTreeSet::default(), encryption_pk) } /// Replace an existing file with the provided one (locally). @@ -137,6 +140,7 @@ impl FoldersApi { existing_entry: EntryHash, file_name: OsString, data_map_chunk: Chunk, + encryption_pk: Option, ) -> Result<(EntryHash, XorName, Metadata)> { // create metadata Chunk for this entry let metadata = Metadata { @@ -144,7 +148,11 @@ impl FoldersApi { content: FolderEntry::File(data_map_chunk), }; - self.add_entry(metadata, &vec![existing_entry].into_iter().collect()) + self.add_entry( + metadata, + &vec![existing_entry].into_iter().collect(), + encryption_pk, + ) } /// Remove a file/folder item from this Folder (locally). @@ -207,7 +215,7 @@ impl FoldersApi { .register .read() .iter() - .map(|(_, meta_xorname)| xorname_from_entry(meta_xorname)) + .map(|(_, meta_xorname_entry)| xorname_from_entry(meta_xorname_entry)) .collect(); self.metadata @@ -240,7 +248,16 @@ impl FoldersApi { .client .get_chunk(ChunkAddress::new(meta_xorname), false, None) .await?; - let metadata: Metadata = rmp_serde::from_slice(chunk.value())?; + + let metadata: Metadata = match rmp_serde::from_slice(chunk.value()) { + Ok(metadata) => metadata, + Err(_) => { + // let's try to decrypt it then + let cipher = Ciphertext::from_bytes(chunk.value()).unwrap(); + let data = self.client.signer().decrypt(&cipher).unwrap(); + rmp_serde::from_slice(&data)? + } + }; self.metadata.insert(meta_xorname, (metadata.clone(), None)); metadata } @@ -265,14 +282,25 @@ impl FoldersApi { }) } - // Add the given entry to the underlying Register as well as creating the metadata Chunk + // Add the given entry to the underlying Register as well as creating the metadata Chunk. + // If an encryption key is given, the metadata chunk will be encrpyted with it. fn add_entry( &mut self, metadata: Metadata, children: &BTreeSet, + encryption_pk: Option, ) -> Result<(EntryHash, XorName, Metadata)> { let mut bytes = BytesMut::with_capacity(MAX_CHUNK_SIZE); - bytes.put(rmp_serde::to_vec(&metadata)?.as_slice()); + let serialised_metadata = rmp_serde::to_vec(&metadata)?; + if let Some(pk) = encryption_pk { + bytes.put( + pk.encrypt(serialised_metadata.as_slice()) + .to_bytes() + .as_slice(), + ); + } else { + bytes.put(serialised_metadata.as_slice()); + } let meta_chunk = Chunk::new(bytes.freeze()); let meta_xorname = *meta_chunk.name();