From 61dbe027cc904a1b72fc4c10b44f2ded437d9013 Mon Sep 17 00:00:00 2001 From: Alexander Weiss Date: Thu, 23 Mar 2023 13:11:02 +0100 Subject: [PATCH 1/2] keyfile: use serde_with::base64 --- Cargo.lock | 1 - Cargo.toml | 3 +-- src/repofile/keyfile.rs | 46 +++++++++++++++++++++-------------------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ad00e58..05ef875 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1829,7 +1829,6 @@ dependencies = [ "aho-corasick", "anyhow", "backoff", - "base64 0.21.0", "binrw", "bytes", "bytesize", diff --git a/Cargo.toml b/Cargo.toml index 77aa6a4..fcb2244 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,10 +42,10 @@ scrypt = { version = "0.11", default-features = false } # cdc = "0.1" integer-sqrt = "0.1" # serialization -base64 = "0.21" binrw = "0.11" hex = { version = "0.4", features = ["serde"] } serde = { version = "1", features = ["derive"] } +serde_with = { version = "2.3", features = ["base64"] } serde_json = "1" serde-aux = "4" # other dependencies @@ -76,7 +76,6 @@ directories = "5" nom = "7" toml = "0.7" merge = "0.1" -serde_with = "2.3" rpassword = "7" bytesize = "1" indicatif = "0.17" diff --git a/src/repofile/keyfile.rs b/src/repofile/keyfile.rs index 5297b9a..a56031a 100644 --- a/src/repofile/keyfile.rs +++ b/src/repofile/keyfile.rs @@ -1,14 +1,16 @@ use anyhow::{anyhow, Result}; -use base64::{engine::general_purpose::STANDARD, Engine as _}; use chrono::{DateTime, Local}; use rand::{thread_rng, RngCore}; use scrypt::Params; use serde::{Deserialize, Serialize}; +use serde_with::base64::Base64; +use serde_with::serde_as; use crate::backend::{FileType, ReadBackend}; use crate::crypto::{CryptoKey, Key}; use crate::id::Id; +#[serde_as] #[serde_with::apply(Option => #[serde(default, skip_serializing_if = "Option::is_none")])] #[derive(Debug, Serialize, Deserialize)] pub struct KeyFile { @@ -20,8 +22,10 @@ pub struct KeyFile { n: u32, r: u32, p: u32, - data: String, - salt: String, + #[serde_as(as = "Base64")] + data: Vec, + #[serde_as(as = "Base64")] + salt: Vec, } impl KeyFile { @@ -29,10 +33,10 @@ impl KeyFile { fn kdf_key(&self, passwd: &impl AsRef<[u8]>) -> Result { let params = Params::new(log_2(self.n), self.r, self.p, Params::RECOMMENDED_LEN) .map_err(|_| anyhow!("invalid scrypt paramters"))?; - let salt = STANDARD.decode(&self.salt)?; let mut key = [0; 64]; - scrypt::scrypt(passwd.as_ref(), &salt, ¶ms, &mut key).expect("output length invalid?"); + scrypt::scrypt(passwd.as_ref(), &self.salt, ¶ms, &mut key) + .expect("output length invalid?"); Ok(Key::from_slice(&key)) } @@ -41,7 +45,7 @@ impl KeyFile { /// The key usually should be the key generated by [`kdf_key()`](Self::kdf_key) fn key_from_data(&self, key: &Key) -> Result { let dec_data = key - .decrypt_data(&STANDARD.decode(&self.data)?) + .decrypt_data(&self.data) .map_err(|_| anyhow!("decryption failed"))?; serde_json::from_slice::(&dec_data)?.key() } @@ -62,7 +66,7 @@ impl KeyFile { ) -> Result { let masterkey = MasterKey::from_key(key); let params = Params::recommended(); - let mut salt = [0; 64]; + let mut salt = vec![0; 64]; thread_rng().fill_bytes(&mut salt); let mut key = [0; 64]; @@ -79,8 +83,8 @@ impl KeyFile { r: params.r(), p: params.p(), created: with_created.then(Local::now), - data: STANDARD.encode(data), - salt: STANDARD.encode(salt), + data, + salt, }) } } @@ -102,36 +106,34 @@ fn log_2(x: u32) -> u8 { num_bits::() as u8 - x.leading_zeros() as u8 - 1 } +#[serde_as] #[derive(Debug, Serialize, Deserialize)] struct Mac { - k: String, - r: String, + #[serde_as(as = "Base64")] + k: Vec, + #[serde_as(as = "Base64")] + r: Vec, } +#[serde_as] #[derive(Debug, Serialize, Deserialize)] struct MasterKey { mac: Mac, - encrypt: String, + #[serde_as(as = "Base64")] + encrypt: Vec, } impl MasterKey { fn from_key(key: Key) -> Self { let (encrypt, k, r) = key.to_keys(); Self { - encrypt: STANDARD.encode(encrypt), - mac: Mac { - k: STANDARD.encode(k), - r: STANDARD.encode(r), - }, + encrypt, + mac: Mac { k, r }, } } fn key(&self) -> Result { - Ok(Key::from_keys( - &STANDARD.decode(&self.encrypt)?, - &STANDARD.decode(&self.mac.k)?, - &STANDARD.decode(&self.mac.r)?, - )) + Ok(Key::from_keys(&self.encrypt, &self.mac.k, &self.mac.r)) } } From e47708479c75e606d673d8948b809751a1c0de11 Mon Sep 17 00:00:00 2001 From: Alexander Weiss Date: Thu, 23 Mar 2023 13:57:09 +0100 Subject: [PATCH 2/2] Handle extended attributes --- Cargo.lock | 12 +++++++++++- Cargo.toml | 1 + src/backend/ignore.rs | 16 ++++++++++++++-- src/backend/local.rs | 31 ++++++++++++++++++++++++++++++- src/backend/node.rs | 12 ++++++++++++ src/commands/restore.rs | 2 ++ 6 files changed, 70 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 05ef875..deed4e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1882,6 +1882,7 @@ dependencies = [ "toml", "users", "walkdir", + "xattr 1.0.0", "zstd", ] @@ -2266,7 +2267,7 @@ checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" dependencies = [ "filetime", "libc", - "xattr", + "xattr 0.2.3", ] [[package]] @@ -2867,6 +2868,15 @@ dependencies = [ "libc", ] +[[package]] +name = "xattr" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea263437ca03c1522846a4ddafbca2542d0ad5ed9b784909d4b27b76f62bc34a" +dependencies = [ + "libc", +] + [[package]] name = "zeroize" version = "1.4.3" diff --git a/Cargo.toml b/Cargo.toml index fcb2244..0cfd9fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,6 +91,7 @@ rhai = {version = "1.13", features = ["sync", "serde", "no_optimize", "no_module [target.'cfg(not(windows))'.dependencies] users = "0.11" +xattr = "1" [dev-dependencies] rstest = "0.17" diff --git a/src/backend/ignore.rs b/src/backend/ignore.rs index 1e9b7b7..bccf68f 100644 --- a/src/backend/ignore.rs +++ b/src/backend/ignore.rs @@ -17,8 +17,8 @@ use serde_with::{serde_as, DisplayFromStr}; #[cfg(not(windows))] use users::{Groups, Users, UsersCache}; -use super::{node::Metadata, node::NodeType, Node, ReadSource}; -use super::{ReadSourceEntry, ReadSourceOpen}; +use super::node::{ExtendedAttribute, Metadata, NodeType}; +use super::{Node, ReadSource, ReadSourceEntry, ReadSourceOpen}; pub struct LocalSource { builder: WalkBuilder, @@ -259,6 +259,7 @@ fn map_entry( inode, device_id, links, + extended_attributes: Vec::new(), }; let node = if m.is_dir() { @@ -322,6 +323,16 @@ fn map_entry( let device_id = if ignore_devid { 0 } else { m.dev() }; let links = if m.is_dir() { 0 } else { m.nlink() }; + let path = entry.path(); + let extended_attributes = xattr::list(path)? + .map(|name| { + Ok(ExtendedAttribute { + name: name.to_string_lossy().to_string(), + value: xattr::get(path, name)?.unwrap(), + }) + }) + .collect::>()?; + let meta = Metadata { size, mtime, @@ -335,6 +346,7 @@ fn map_entry( inode, device_id, links, + extended_attributes, }; let filetype = m.file_type(); diff --git a/src/backend/local.rs b/src/backend/local.rs index ee20ad3..591eb12 100644 --- a/src/backend/local.rs +++ b/src/backend/local.rs @@ -22,7 +22,7 @@ use crate::repository::parse_command; use super::mapper::map_mode_from_go; #[cfg(not(windows))] use super::node::NodeType; -use super::node::{Metadata, Node}; +use super::node::{ExtendedAttribute, Metadata, Node}; use super::{FileType, Id, ReadBackend, WriteBackend, ALL_FILE_TYPES}; #[derive(Clone)] @@ -324,6 +324,35 @@ impl LocalDestination { Ok(()) } + #[cfg(windows)] + pub fn set_extended_attributes( + &self, + item: impl AsRef, + extended_attributes: &[ExtendedAttribute], + ) -> Result<()> { + Ok(()) + } + + #[cfg(not(windows))] + pub fn set_extended_attributes( + &self, + item: impl AsRef, + extended_attributes: &[ExtendedAttribute], + ) -> Result<()> { + let filename = self.path(item); + + for name in xattr::list(&filename)? { + if let Err(err) = xattr::remove(&filename, &name) { + warn!("error removing xattr on {filename:?}: {err}"); + } + } + + for ExtendedAttribute { name, value } in extended_attributes { + xattr::set(&filename, name, value)?; + } + Ok(()) + } + // set_length sets the length of the given file. If it doesn't exist, create a new (empty) one with given length pub fn set_length(&self, item: impl AsRef, size: u64) -> Result<()> { let filename = self.path(item); diff --git a/src/backend/node.rs b/src/backend/node.rs index f148161..602f92b 100644 --- a/src/backend/node.rs +++ b/src/backend/node.rs @@ -14,6 +14,8 @@ use derive_getters::Getters; use derive_more::{Constructor, IsVariant}; use serde::{Deserialize, Serialize}; use serde_aux::prelude::*; +use serde_with::base64::Base64; +use serde_with::serde_as; use crate::id::Id; @@ -68,6 +70,16 @@ pub struct Metadata { pub device_id: u64, pub size: u64, pub links: u64, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub extended_attributes: Vec, +} + +#[serde_as] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct ExtendedAttribute { + pub name: String, + #[serde_as(as = "Base64")] + pub value: Vec, } fn is_default(t: &T) -> bool { diff --git a/src/commands/restore.rs b/src/commands/restore.rs index b4fb435..d9b7b2f 100644 --- a/src/commands/restore.rs +++ b/src/commands/restore.rs @@ -453,6 +453,8 @@ fn set_metadata(dest: &LocalDestination, path: &PathBuf, node: &Node, opts: &Opt } dest.set_permission(path, node.meta()) .unwrap_or_else(|_| warn!("restore {:?}: chmod failed.", path)); + dest.set_extended_attributes(path, &node.meta.extended_attributes) + .unwrap_or_else(|_| warn!("restore {:?}: setting extended attributes failed.", path)); dest.set_times(path, node.meta()) .unwrap_or_else(|_| warn!("restore {:?}: setting file times failed.", path)); }