Skip to content

Commit 20c7054

Browse files
committed
refactor!: Add more control over used keys
1 parent 6a50c12 commit 20c7054

File tree

8 files changed

+149
-79
lines changed

8 files changed

+149
-79
lines changed

crates/core/src/backend/decrypt.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,11 @@ pub trait DecryptReadBackend: ReadBackend + Clone + 'static {
134134
///
135135
/// * If the file could not be read.
136136
fn get_file<F: RepoFile>(&self, id: &Id) -> RusticResult<F> {
137-
let data = self.read_encrypted_full(F::TYPE, id)?;
137+
let data = if F::ENCRYPTED {
138+
self.read_encrypted_full(F::TYPE, id)?
139+
} else {
140+
self.read_full(F::TYPE, id)?
141+
};
138142
let deserialized = serde_json::from_slice(&data).map_err(|err| {
139143
RusticError::with_source(
140144
ErrorKind::Internal,
@@ -262,7 +266,14 @@ pub trait DecryptWriteBackend: WriteBackend + Clone + 'static {
262266
.ask_report()
263267
})?;
264268

265-
self.hash_write_full(F::TYPE, &data)
269+
if F::ENCRYPTED {
270+
self.hash_write_full(F::TYPE, &data)
271+
} else {
272+
let id = hash(&data);
273+
274+
self.write_bytes(F::TYPE, &id, false, data.into())?;
275+
Ok(id)
276+
}
266277
}
267278

268279
/// Saves the given file uncompressed.

crates/core/src/commands/check.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ pub(crate) fn check_repository<P: ProgressBars, S: Open>(
238238
trees: Vec<TreeId>,
239239
) -> RusticResult<()> {
240240
let be = repo.dbe();
241-
let cache = repo.cache();
241+
let cache = &repo.open_status().cache;
242242
let hot_be = &repo.be_hot;
243243
let raw_be = repo.dbe();
244244
let pb = &repo.pb;

crates/core/src/commands/init.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::{
1212
crypto::aespoly1305::Key,
1313
error::RusticResult,
1414
id::Id,
15-
repofile::{configfile::RepositoryId, ConfigFile},
15+
repofile::{configfile::RepositoryId, ConfigFile, KeyId},
1616
repository::Repository,
1717
};
1818

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

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

60-
Ok((key, config))
60+
Ok((key, key_id, config))
6161
}
6262

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

91-
Ok(key)
91+
Ok((key, id))
9292
}

crates/core/src/repofile.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ pub(crate) mod keyfile;
88
pub(crate) mod packfile;
99
pub(crate) mod snapshotfile;
1010

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

crates/core/src/repofile/keyfile.rs

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::{
99
crypto::{aespoly1305::Key, CryptoKey},
1010
error::{ErrorKind, RusticError, RusticResult},
1111
impl_repoid,
12+
repofile::RepoFile,
1213
};
1314

1415
/// [`KeyFileErrorKind`] describes the errors that can be returned for `KeyFile`s
@@ -45,34 +46,40 @@ impl_repoid!(KeyId, FileType::Key);
4546
#[derive(Serialize, Deserialize, Debug)]
4647
pub struct KeyFile {
4748
/// Hostname where the key was created
48-
hostname: Option<String>,
49+
pub hostname: Option<String>,
4950

5051
/// User which created the key
51-
username: Option<String>,
52+
pub username: Option<String>,
5253

5354
/// Creation time of the key
54-
created: Option<DateTime<Local>>,
55+
pub created: Option<DateTime<Local>>,
5556

5657
/// The used key derivation function (currently only `scrypt`)
57-
kdf: String,
58+
pub kdf: String,
5859

5960
/// Parameter N for `scrypt`
6061
#[serde(rename = "N")]
61-
n: u32,
62+
pub n: u32,
6263

6364
/// Parameter r for `scrypt`
64-
r: u32,
65+
pub r: u32,
6566

6667
/// Parameter p for `scrypt`
67-
p: u32,
68+
pub p: u32,
6869

6970
/// The key data encrypted by `scrypt`
7071
#[serde_as(as = "Base64")]
71-
data: Vec<u8>,
72+
pub data: Vec<u8>,
7273

7374
/// The salt used with `scrypt`
7475
#[serde_as(as = "Base64")]
75-
salt: Vec<u8>,
76+
pub salt: Vec<u8>,
77+
}
78+
79+
impl RepoFile for KeyFile {
80+
const TYPE: FileType = FileType::Key;
81+
const ENCRYPTED: bool = false;
82+
type Id = KeyId;
7683
}
7784

7885
impl KeyFile {
@@ -386,15 +393,15 @@ pub(crate) fn find_key_in_backend<B: ReadBackend>(
386393
be: &B,
387394
passwd: &impl AsRef<[u8]>,
388395
hint: Option<&KeyId>,
389-
) -> RusticResult<Key> {
396+
) -> RusticResult<(Key, KeyId)> {
390397
if let Some(id) = hint {
391-
key_from_backend(be, id, passwd)
398+
Ok((key_from_backend(be, id, passwd)?, *id))
392399
} else {
393400
for id in be.list(FileType::Key)? {
394401
match key_from_backend(be, &id.into(), passwd) {
395-
Ok(key) => return Ok(key),
402+
Ok(key) => return Ok((key, KeyId(id))),
396403
Err(err) if err.is_code("C001") => continue,
397-
err => return err,
404+
Err(err) => return Err(err),
398405
}
399406
}
400407

crates/core/src/repository.rs

Lines changed: 53 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use crate::{
2323
local_destination::LocalDestination,
2424
node::Node,
2525
warm_up::WarmUpAccessBackend,
26-
FileType, ReadBackend, WriteBackend,
26+
FileType, FindInBackend, ReadBackend, WriteBackend,
2727
},
2828
blob::{
2929
tree::{FindMatches, FindNode, NodeStreamer, TreeId, TreeStreamerOptions as LsOptions},
@@ -520,13 +520,13 @@ impl<P, S> Repository<P, S> {
520520
}
521521
}
522522

523-
let key = find_key_in_backend(&self.be, &password, None)?;
523+
let (key, key_id) = find_key_in_backend(&self.be, &password, None)?;
524524

525525
info!("repository {}: password is correct.", self.name);
526526

527527
let dbe = DecryptBackend::new(self.be.clone(), key);
528528
let config: ConfigFile = dbe.get_file(&config_id)?;
529-
self.open_raw(key, config)
529+
self.open_raw(key, key_id, config)
530530
}
531531

532532
/// Initialize a new repository with given options using the password defined in `RepositoryOptions`
@@ -604,9 +604,9 @@ impl<P, S> Repository<P, S> {
604604
.attach_context("name", self.name));
605605
}
606606

607-
let (key, config) = commands::init::init(&self, pass, key_opts, config_opts)?;
607+
let (key, key_id, config) = commands::init::init(&self, pass, key_opts, config_opts)?;
608608

609-
self.open_raw(key, config)
609+
self.open_raw(key, key_id, config)
610610
}
611611

612612
/// Initialize a new repository with given password and a ready [`ConfigFile`].
@@ -632,9 +632,9 @@ impl<P, S> Repository<P, S> {
632632
key_opts: &KeyOptions,
633633
config: ConfigFile,
634634
) -> RusticResult<Repository<P, OpenStatus>> {
635-
let key = commands::init::init_with_config(&self, password, key_opts, &config)?;
635+
let (key, key_id) = commands::init::init_with_config(&self, password, key_opts, &config)?;
636636
info!("repository {} successfully created.", config.id);
637-
self.open_raw(key, config)
637+
self.open_raw(key, key_id, config)
638638
}
639639

640640
/// Open the repository with given [`Key`] and [`ConfigFile`].
@@ -652,7 +652,12 @@ impl<P, S> Repository<P, S> {
652652
///
653653
/// * If the config file has `is_hot` set to `true` but the repository is not hot
654654
/// * If the config file has `is_hot` set to `false` but the repository is hot
655-
fn open_raw(mut self, key: Key, config: ConfigFile) -> RusticResult<Repository<P, OpenStatus>> {
655+
fn open_raw(
656+
mut self,
657+
key: Key,
658+
key_id: KeyId,
659+
config: ConfigFile,
660+
) -> RusticResult<Repository<P, OpenStatus>> {
656661
match (config.is_hot == Some(true), self.be_hot.is_some()) {
657662
(true, false) => return Err(
658663
RusticError::new(
@@ -684,7 +689,12 @@ impl<P, S> Repository<P, S> {
684689
dbe.set_zstd(config.zstd()?);
685690
dbe.set_extra_verify(config.extra_verify());
686691

687-
let open = OpenStatus { cache, dbe, config };
692+
let open = OpenStatus {
693+
cache,
694+
dbe,
695+
config,
696+
key_id,
697+
};
688698

689699
Ok(Repository {
690700
name: self.name,
@@ -755,58 +765,32 @@ impl<P: ProgressBars, S> Repository<P, S> {
755765

756766
/// A repository which is open, i.e. the password has been checked and the decryption key is available.
757767
pub trait Open {
758-
/// Get the cache
759-
fn cache(&self) -> Option<&Cache>;
760-
761-
/// Get the [`DecryptBackend`]
762-
fn dbe(&self) -> &DecryptBackend<Key>;
763-
764-
/// Get the [`ConfigFile`]
765-
fn config(&self) -> &ConfigFile;
768+
/// Get the open status
769+
fn open_status(&self) -> &OpenStatus;
766770
}
767771

768772
impl<P, S: Open> Open for Repository<P, S> {
769-
/// Get the cache
770-
fn cache(&self) -> Option<&Cache> {
771-
self.status.cache()
772-
}
773-
774-
/// Get the [`DecryptBackend`]
775-
fn dbe(&self) -> &DecryptBackend<Key> {
776-
self.status.dbe()
777-
}
778-
779-
/// Get the [`ConfigFile`]
780-
fn config(&self) -> &ConfigFile {
781-
self.status.config()
773+
fn open_status(&self) -> &OpenStatus {
774+
self.status.open_status()
782775
}
783776
}
784777

785778
/// Open Status: This repository is open, i.e. the password has been checked and the decryption key is available.
786779
#[derive(Debug)]
787780
pub struct OpenStatus {
788781
/// The cache
789-
cache: Option<Cache>,
782+
pub(crate) cache: Option<Cache>,
790783
/// The [`DecryptBackend`]
791784
dbe: DecryptBackend<Key>,
792785
/// The [`ConfigFile`]
793786
config: ConfigFile,
787+
/// The [`KeyId`] of the used key
788+
key_id: KeyId,
794789
}
795790

796791
impl Open for OpenStatus {
797-
/// Get the cache
798-
fn cache(&self) -> Option<&Cache> {
799-
self.cache.as_ref()
800-
}
801-
802-
/// Get the [`DecryptBackend`]
803-
fn dbe(&self) -> &DecryptBackend<Key> {
804-
&self.dbe
805-
}
806-
807-
/// Get the [`ConfigFile`]
808-
fn config(&self) -> &ConfigFile {
809-
&self.config
792+
fn open_status(&self) -> &OpenStatus {
793+
self
810794
}
811795
}
812796

@@ -863,12 +847,33 @@ impl<P, S: Open> Repository<P, S> {
863847

864848
/// Get the repository configuration
865849
pub fn config(&self) -> &ConfigFile {
866-
self.status.config()
850+
&self.open_status().config
867851
}
868852

869853
// TODO: add documentation!
870854
pub(crate) fn dbe(&self) -> &DecryptBackend<Key> {
871-
self.status.dbe()
855+
&self.open_status().dbe
856+
}
857+
858+
/// Get the [`KeyId`] of the key used to open the repository
859+
pub fn key_id(&self) -> &KeyId {
860+
&self.open_status().key_id
861+
}
862+
863+
/// Delete the key with id starting with the given string from the repository.
864+
///
865+
/// # Errors
866+
///
867+
/// * If the key could not be removed.
868+
pub fn delete_key(&self, id: &str) -> RusticResult<()> {
869+
let id = self.dbe().find_id(FileType::Key, id)?;
870+
if self.key_id() == &KeyId::from(id) {
871+
return Err(RusticError::new(
872+
ErrorKind::Repository,
873+
"Cannot remove the currently used key",
874+
));
875+
}
876+
self.dbe().remove(FileType::Key, &id, false)
872877
}
873878
}
874879

@@ -1557,16 +1562,8 @@ impl<P, S: IndexedFull> IndexedFull for Repository<P, S> {
15571562
}
15581563

15591564
impl<T, S: Open> Open for IndexedStatus<T, S> {
1560-
fn cache(&self) -> Option<&Cache> {
1561-
self.open.cache()
1562-
}
1563-
1564-
fn dbe(&self) -> &DecryptBackend<Key> {
1565-
self.open.dbe()
1566-
}
1567-
1568-
fn config(&self) -> &ConfigFile {
1569-
self.open.config()
1565+
fn open_status(&self) -> &OpenStatus {
1566+
self.open.open_status()
15701567
}
15711568
}
15721569

crates/core/tests/integration.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
//! The fixtures are defined as functions with the `#[fixture]` attribute.
2323
//! The tests that use the fixtures are defined as functions with the `#[rstest]` attribute.
2424
//! The fixtures are passed as arguments to the test functions.
25+
mod integration {
26+
use super::*;
27+
mod key;
28+
}
2529

2630
use anyhow::Result;
2731
use bytes::Bytes;

0 commit comments

Comments
 (0)