Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(repository)!: Add more control over used keys #383

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions crates/core/src/backend/decrypt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,12 @@
/// # Errors
///
/// * If the file could not be read.
fn get_file<F: RepoFile>(&self, id: &Id) -> RusticResult<F> {
let data = self.read_encrypted_full(F::TYPE, id)?;
fn get_file<F: RepoFile>(&self, id: &F::Id) -> RusticResult<F> {
let data = if F::ENCRYPTED {

Check warning on line 137 in crates/core/src/backend/decrypt.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/backend/decrypt.rs#L137

Added line #L137 was not covered by tests
self.read_encrypted_full(F::TYPE, id)?
} else {
self.read_full(F::TYPE, id)?
};
let deserialized = serde_json::from_slice(&data).map_err(|err| {
RusticError::with_source(
ErrorKind::Internal,
Expand All @@ -159,6 +163,7 @@
/// If the files could not be read.
fn stream_all<F: RepoFile>(&self, p: &impl Progress) -> StreamResult<F::Id, F> {
let list = self.list(F::TYPE)?;
let list: Vec<_> = list.into_iter().map(F::Id::from).collect();

Check warning on line 166 in crates/core/src/backend/decrypt.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/backend/decrypt.rs#L166

Added line #L166 was not covered by tests
self.stream_list(&list, p)
}

Expand All @@ -174,13 +179,17 @@
/// # Errors
///
/// If the files could not be read.
fn stream_list<F: RepoFile>(&self, list: &[Id], p: &impl Progress) -> StreamResult<F::Id, F> {
fn stream_list<F: RepoFile>(
&self,
list: &[F::Id],
p: &impl Progress,
) -> StreamResult<F::Id, F> {
p.set_length(list.len() as u64);
let (tx, rx) = unbounded();

list.into_par_iter()
.for_each_with((self, p, tx), |(be, p, tx), id| {
let file = be.get_file::<F>(id).map(|file| (F::Id::from(*id), file));
let file = be.get_file::<F>(id).map(|file| (*id, file));
p.inc(1);
tx.send(file).unwrap();
});
Expand Down Expand Up @@ -262,7 +271,14 @@
.ask_report()
})?;

self.hash_write_full(F::TYPE, &data)
if F::ENCRYPTED {

Check warning on line 274 in crates/core/src/backend/decrypt.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/backend/decrypt.rs#L274

Added line #L274 was not covered by tests
self.hash_write_full(F::TYPE, &data)
} else {
let id = hash(&data);

Check warning on line 277 in crates/core/src/backend/decrypt.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/backend/decrypt.rs#L277

Added line #L277 was not covered by tests

self.write_bytes(F::TYPE, &id, false, data.into())?;
Ok(id)

Check warning on line 280 in crates/core/src/backend/decrypt.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/backend/decrypt.rs#L279-L280

Added lines #L279 - L280 were not covered by tests
}
}

/// Saves the given file uncompressed.
Expand Down
2 changes: 1 addition & 1 deletion crates/core/src/commands/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@
trees: Vec<TreeId>,
) -> RusticResult<()> {
let be = repo.dbe();
let cache = repo.cache();
let cache = &repo.open_status().cache;

Check warning on line 241 in crates/core/src/commands/check.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/commands/check.rs#L241

Added line #L241 was not covered by tests
let hot_be = &repo.be_hot;
let raw_be = repo.dbe();
let pb = &repo.pb;
Expand Down
12 changes: 6 additions & 6 deletions crates/core/src/commands/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::{
crypto::aespoly1305::Key,
error::RusticResult,
id::Id,
repofile::{configfile::RepositoryId, ConfigFile},
repofile::{configfile::RepositoryId, ConfigFile, KeyId},
repository::Repository,
};

Expand Down Expand Up @@ -42,7 +42,7 @@ pub(crate) fn init<P, S>(
pass: &str,
key_opts: &KeyOptions,
config_opts: &ConfigOptions,
) -> RusticResult<(Key, ConfigFile)> {
) -> RusticResult<(Key, KeyId, ConfigFile)> {
// Create config first to allow catching errors from here without writing anything
let repo_id = RepositoryId::from(Id::random());
let chunker_poly = random_poly()?;
Expand All @@ -54,10 +54,10 @@ pub(crate) fn init<P, S>(
}
config_opts.apply(&mut config)?;

let key = init_with_config(repo, pass, key_opts, &config)?;
let (key, key_id) = init_with_config(repo, pass, key_opts, &config)?;
info!("repository {} successfully created.", repo_id);

Ok((key, config))
Ok((key, key_id, config))
}

/// Initialize a new repository with a given config.
Expand All @@ -82,11 +82,11 @@ pub(crate) fn init_with_config<P, S>(
pass: &str,
key_opts: &KeyOptions,
config: &ConfigFile,
) -> RusticResult<Key> {
) -> RusticResult<(Key, KeyId)> {
repo.be.create()?;
let (key, id) = init_key(repo, key_opts, pass)?;
info!("key {id} successfully added.");
save_config(repo, config.clone(), key)?;

Ok(key)
Ok((key, id))
}
3 changes: 2 additions & 1 deletion crates/core/src/commands/prune.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1587,7 +1587,8 @@ fn find_used_blobs(
let list: Vec<_> = be
.list(FileType::Snapshot)?
.into_iter()
.filter(|id| !ignore_snaps.contains(&SnapshotId::from(*id)))
.map(SnapshotId::from)
.filter(|id| !ignore_snaps.contains(&id))
.collect();
let snap_trees: Vec<_> = be
.stream_list::<SnapshotFile>(&list, &p)?
Expand Down
8 changes: 5 additions & 3 deletions crates/core/src/repofile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,18 @@ pub(crate) mod keyfile;
pub(crate) mod packfile;
pub(crate) mod snapshotfile;

/// Marker trait for repository files which are stored as encrypted JSON
/// Marker trait for repository files which are stored as JSON
pub trait RepoFile: Serialize + DeserializeOwned + Sized + Send + Sync + 'static {
/// The [`FileType`] associated with the repository file
const TYPE: FileType;
/// Indicate whether the files are stored encrypted
const ENCRYPTED: bool = true;
/// The Id type associated with the repository file
type Id: From<Id> + Send;
type Id: RepoId;
}

/// Marker trait for Ids which identify repository files
pub trait RepoId: Deref<Target = Id> + From<Id> + Sized + Send + Sync + 'static {
pub trait RepoId: Deref<Target = Id> + From<Id> + Sized + Copy + Send + Sync + 'static {
/// The [`FileType`] associated with Id type
const TYPE: FileType;
}
Expand Down
33 changes: 20 additions & 13 deletions crates/core/src/repofile/keyfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
crypto::{aespoly1305::Key, CryptoKey},
error::{ErrorKind, RusticError, RusticResult},
impl_repoid,
repofile::RepoFile,
};

/// [`KeyFileErrorKind`] describes the errors that can be returned for `KeyFile`s
Expand Down Expand Up @@ -45,34 +46,40 @@
#[derive(Serialize, Deserialize, Debug)]
pub struct KeyFile {
/// Hostname where the key was created
hostname: Option<String>,
pub hostname: Option<String>,

/// User which created the key
username: Option<String>,
pub username: Option<String>,

/// Creation time of the key
created: Option<DateTime<Local>>,
pub created: Option<DateTime<Local>>,

/// The used key derivation function (currently only `scrypt`)
kdf: String,
pub kdf: String,

/// Parameter N for `scrypt`
#[serde(rename = "N")]
n: u32,
pub n: u32,

/// Parameter r for `scrypt`
r: u32,
pub r: u32,

/// Parameter p for `scrypt`
p: u32,
pub p: u32,

/// The key data encrypted by `scrypt`
#[serde_as(as = "Base64")]
data: Vec<u8>,
pub data: Vec<u8>,

/// The salt used with `scrypt`
#[serde_as(as = "Base64")]
salt: Vec<u8>,
pub salt: Vec<u8>,
}

impl RepoFile for KeyFile {
const TYPE: FileType = FileType::Key;
const ENCRYPTED: bool = false;
type Id = KeyId;
}

impl KeyFile {
Expand Down Expand Up @@ -386,15 +393,15 @@
be: &B,
passwd: &impl AsRef<[u8]>,
hint: Option<&KeyId>,
) -> RusticResult<Key> {
) -> RusticResult<(Key, KeyId)> {
if let Some(id) = hint {
key_from_backend(be, id, passwd)
Ok((key_from_backend(be, id, passwd)?, *id))

Check warning on line 398 in crates/core/src/repofile/keyfile.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/repofile/keyfile.rs#L398

Added line #L398 was not covered by tests
} else {
for id in be.list(FileType::Key)? {
match key_from_backend(be, &id.into(), passwd) {
Ok(key) => return Ok(key),
Ok(key) => return Ok((key, KeyId(id))),
Err(err) if err.is_code("C001") => continue,
err => return err,
Err(err) => return Err(err),
}
}

Expand Down
4 changes: 2 additions & 2 deletions crates/core/src/repofile/snapshotfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -671,8 +671,8 @@
let mut snaps: BTreeMap<_, _> = current.into_iter().map(|snap| (snap.id, snap)).collect();
let missing_ids: Vec<_> = ids
.iter()
.filter(|id| !snaps.contains_key(&SnapshotId::from(**id)))
.copied()
.map(|id| SnapshotId::from(*id))

Check warning on line 674 in crates/core/src/repofile/snapshotfile.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/repofile/snapshotfile.rs#L674

Added line #L674 was not covered by tests
.filter(|id| !snaps.contains_key(id))
.collect();
for res in be.stream_list::<Self>(&missing_ids, p)? {
let (id, snap) = res?;
Expand Down
Loading
Loading