rustic_core 0.11.0

rustic_core - library for fast, encrypted, deduplicated backups that powers rustic-rs
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
use jiff::Zoned;
use rand::{Rng, rng};
use scrypt::Params;
use serde_derive::{Deserialize, Serialize};
use serde_with::{base64::Base64, serde_as, skip_serializing_none};

use crate::{
    backend::{FileType, ReadBackend},
    crypto::{CryptoKey, aespoly1305::Key},
    error::{ErrorKind, RusticError, RusticResult},
    impl_repoid,
    repofile::{RepoFile, RusticTime},
};

/// [`KeyFileErrorKind`] describes the errors that can be returned for `KeyFile`s
#[derive(thiserror::Error, Debug, displaydoc::Display)]
#[non_exhaustive]
pub enum KeyFileErrorKind {
    /// conversion from `{from}` to `{to}` failed for `{x}` : `{source}`
    ConversionFailed {
        from: &'static str,
        to: &'static str,
        x: u32,
        source: std::num::TryFromIntError,
    },
}

pub(crate) type KeyFileResult<T> = Result<T, KeyFileErrorKind>;

pub(super) mod constants {
    /// Returns the number of bits of the given type.
    pub(super) const fn num_bits<T>() -> usize {
        // Needed for MSRV 1.76
        #![allow(unused_qualifications)]
        std::mem::size_of::<T>() * 8
    }
}

impl_repoid!(KeyId, FileType::Key);

/// Key files describe information about repository access keys.
///
/// They are usually stored in the repository under `/keys/<ID>`
#[serde_as]
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
pub struct KeyFile {
    /// Hostname where the key was created
    pub hostname: Option<String>,

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

    #[serde_as(as = "Option<RusticTime>")]
    /// Creation time of the key
    pub created: Option<Zoned>,

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

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

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

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

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

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

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

impl KeyFile {
    /// Generate a Key using the key derivation function from [`KeyFile`] and a given password
    ///
    /// # Arguments
    ///
    /// * `passwd` - The password to use for the key derivation function
    ///
    /// # Errors
    ///
    /// * If the parameters of the key derivation function are invalid
    /// * If the output length of the key derivation function is invalid
    ///
    /// # Returns
    ///
    /// The generated key
    pub fn kdf_key(&self, passwd: &impl AsRef<[u8]>) -> RusticResult<Key> {
        let params = Params::new(
            log_2(self.n).map_err(|err| {
                RusticError::with_source(
                    ErrorKind::Internal,
                    "Calculating log2 failed. Please check the key file and password.",
                    err,
                )
            })?,
            self.r,
            self.p,
            Params::RECOMMENDED_LEN,
        )
        .map_err(|err| {
            RusticError::with_source(
                ErrorKind::Key,
                "Invalid scrypt parameters. Please check the key file and password.",
                err,
            )
        })?;

        let mut key = [0; 64];
        scrypt::scrypt(passwd.as_ref(), &self.salt, &params, &mut key).map_err(|err| {
            RusticError::with_source(
                ErrorKind::Key,
                "Output length invalid. Please check the key file and password.",
                err,
            )
        })?;

        Ok(Key::from_slice(&key))
    }

    /// Extract a key from the data of the [`KeyFile`] using the given key.
    /// The key usually should be the key generated by [`kdf_key()`](Self::kdf_key)
    ///
    /// # Arguments
    ///
    /// * `key` - The key to use for decryption
    ///
    /// # Errors
    ///
    /// * If the data could not be deserialized
    ///
    /// # Returns
    ///
    /// The extracted key
    pub fn key_from_data(&self, key: &Key) -> RusticResult<Key> {
        let dec_data = key.decrypt_data(&self.data)?;

        let key = serde_json::from_slice::<MasterKey>(&dec_data)
            .map_err(|err| {
                RusticError::with_source(
                    ErrorKind::Key,
                    "Deserializing master key from slice failed. Please check the key file.",
                    err,
                )
            })?
            .key();

        Ok(key)
    }

    /// Extract a key from the data of the [`KeyFile`] using the key
    /// from the derivation function in combination with the given password.
    ///
    /// # Arguments
    ///
    /// * `passwd` - The password to use for the key derivation function
    ///
    /// # Errors
    ///
    /// * If the parameters of the key derivation function are invalid
    ///
    /// # Returns
    ///
    /// The extracted key
    pub fn key_from_password(&self, passwd: &impl AsRef<[u8]>) -> RusticResult<Key> {
        self.key_from_data(&self.kdf_key(passwd)?)
    }

    /// Generate a new [`KeyFile`] from a given key and password.
    ///
    /// # Arguments
    ///
    /// * `key` - The key to use for encryption
    /// * `passwd` - The password to use for the key derivation function
    /// * `hostname` - The hostname to use for the [`KeyFile`]
    /// * `username` - The username to use for the [`KeyFile`]
    /// * `with_created` - Whether to set the creation time of the [`KeyFile`] to the current time
    ///
    /// # Errors
    ///
    /// * If the output length of the key derivation function is invalid
    /// * If the [`KeyFile`] could not be serialized
    ///
    /// # Returns
    ///
    /// The generated [`KeyFile`]
    pub fn generate(
        key: Key,
        passwd: &impl AsRef<[u8]>,
        hostname: Option<String>,
        username: Option<String>,
        with_created: bool,
    ) -> RusticResult<Self> {
        let masterkey = MasterKey::from_key(key);
        let params = Params::recommended();
        let mut salt = vec![0; 64];
        rng().fill_bytes(&mut salt);

        let mut key = [0; 64];
        scrypt::scrypt(passwd.as_ref(), &salt, &params, &mut key).map_err(|err| {
            RusticError::with_source(
                ErrorKind::Key,
                "Output length invalid. Please check the key file and password.",
                err,
            )
        })?;

        let key = Key::from_slice(&key);

        let json_byte_vec = serde_json::to_vec(&masterkey).map_err(|err| {
            RusticError::with_source(
                ErrorKind::Key,
                "Could not serialize as JSON byte vector.",
                err,
            )
            .ask_report()
        })?;

        let data = key.encrypt_data(&json_byte_vec)?;

        Ok(Self {
            hostname,
            username,
            kdf: "scrypt".to_string(),
            n: 2_u32.pow(u32::from(params.log_n())),
            r: params.r(),
            p: params.p(),
            created: with_created.then(Zoned::now),
            data,
            salt,
        })
    }

    /// Get a [`KeyFile`] from the backend
    ///
    /// # Arguments
    ///
    /// * `be` - The backend to use
    /// * `id` - The id of the [`KeyFile`]
    ///
    /// # Errors
    ///
    /// * If the [`KeyFile`] could not be deserialized/read from the backend
    ///
    /// # Returns
    ///
    /// The [`KeyFile`] read from the backend
    fn from_backend<B: ReadBackend>(be: &B, id: &KeyId) -> RusticResult<Self> {
        let data = be.read_full(FileType::Key, id)?;

        serde_json::from_slice(&data).map_err(|err| {
            RusticError::with_source(
                ErrorKind::Key,
                "Couldn't deserialize the data for key `{key_id}`.",
                err,
            )
            .attach_context("key_id", id.to_string())
        })
    }
}

/// Calculate the logarithm to base 2 of the given number
///
/// # Arguments
///
/// * `x` - The number to calculate the logarithm to base 2 of
///
/// # Errors
///
/// * If the conversion from `u32` to `u8` failed
///
/// # Returns
///
/// The logarithm to base 2 of the given number
fn log_2(x: u32) -> KeyFileResult<u8> {
    assert!(x > 0);
    Ok(u8::try_from(constants::num_bits::<u32>()).map_err(|err| {
        KeyFileErrorKind::ConversionFailed {
            from: "usize",
            to: "u8",
            x,
            source: err,
        }
    })? - u8::try_from(x.leading_zeros()).map_err(|err| KeyFileErrorKind::ConversionFailed {
        from: "u32",
        to: "u8",
        x,
        source: err,
    })? - 1)
}

/// The mac of a [`Key`]
///
/// This is used to verify the integrity of the key
#[serde_as]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Mac {
    /// The key used for the mac
    #[serde_as(as = "Base64")]
    pub k: Vec<u8>,

    /// The random value used for the mac
    #[serde_as(as = "Base64")]
    pub r: Vec<u8>,
}

/// The master key of a [`Key`]
///
/// This is used to encrypt the key
#[serde_as]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MasterKey {
    /// The mac of the key
    pub mac: Mac,
    /// The encrypted key
    #[serde_as(as = "Base64")]
    pub encrypt: Vec<u8>,
}

impl Default for MasterKey {
    fn default() -> Self {
        Self::from_key(Key::new())
    }
}

impl MasterKey {
    /// Create a random [`MasterKey`]
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    /// Create a [`MasterKey`] from a [`Key`]
    ///
    /// # Arguments
    ///
    /// * `key` - The key to create the [`MasterKey`] from
    ///
    /// # Returns
    ///
    /// The created [`MasterKey`]
    pub(crate) fn from_key(key: Key) -> Self {
        let (encrypt, k, r) = key.to_keys();
        Self {
            encrypt,
            mac: Mac { k, r },
        }
    }

    /// Get the [`Key`] from the [`MasterKey`]
    pub(crate) fn key(&self) -> Key {
        Key::from_keys(&self.encrypt, &self.mac.k, &self.mac.r)
    }
}

/// Get a [`KeyFile`] from the backend
///
/// # Arguments
///
/// * `be` - The backend to use
/// * `id` - The id of the [`KeyFile`]
/// * `passwd` - The password to use
///
/// # Errors
///
// TODO!: Add errors!
pub(crate) fn key_from_backend<B: ReadBackend>(
    be: &B,
    id: &KeyId,
    passwd: &impl AsRef<[u8]>,
) -> RusticResult<Key> {
    KeyFile::from_backend(be, id)?.key_from_password(passwd)
}

/// Find a [`KeyFile`] in the backend that fits to the given password and return the contained key.
/// If a key hint is given, only this key is tested.
/// This is recommended for a large number of keys.
///
/// # Arguments
///
/// * `be` - The backend to use
/// * `passwd` - The password to use
/// * `hint` - The key hint to use
///
/// # Errors
///
/// * If no suitable key was found
///
/// # Returns
///
/// The found key
pub(crate) fn find_key_in_backend<B: ReadBackend>(
    be: &B,
    passwd: &impl AsRef<[u8]>,
    hint: Option<&KeyId>,
) -> RusticResult<(Key, KeyId)> {
    if let Some(id) = hint {
        Ok((key_from_backend(be, id, passwd)?, *id))
    } else {
        for id in be.list(FileType::Key)? {
            match key_from_backend(be, &id.into(), passwd) {
                Ok(key) => return Ok((key, KeyId(id))),
                Err(err) if err.is_code("C001") => {}
                Err(err) => return Err(err),
            }
        }

        Err(RusticError::new(
            ErrorKind::Credentials,
            "The password that has been entered, seems to be incorrect. No suitable key found for the given password. Please check your password and try again.",
        ).attach_error_code("C002"))
    }
}