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},
};
#[derive(thiserror::Error, Debug, displaydoc::Display)]
#[non_exhaustive]
pub enum KeyFileErrorKind {
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 {
pub(super) const fn num_bits<T>() -> usize {
#![allow(unused_qualifications)]
std::mem::size_of::<T>() * 8
}
}
impl_repoid!(KeyId, FileType::Key);
#[serde_as]
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
pub struct KeyFile {
pub hostname: Option<String>,
pub username: Option<String>,
#[serde_as(as = "Option<RusticTime>")]
pub created: Option<Zoned>,
pub kdf: String,
#[serde(rename = "N")]
pub n: u32,
pub r: u32,
pub p: u32,
#[serde_as(as = "Base64")]
pub data: Vec<u8>,
#[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 {
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, ¶ms, &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))
}
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)
}
pub fn key_from_password(&self, passwd: &impl AsRef<[u8]>) -> RusticResult<Key> {
self.key_from_data(&self.kdf_key(passwd)?)
}
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, ¶ms, &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,
})
}
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())
})
}
}
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)
}
#[serde_as]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Mac {
#[serde_as(as = "Base64")]
pub k: Vec<u8>,
#[serde_as(as = "Base64")]
pub r: Vec<u8>,
}
#[serde_as]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MasterKey {
pub mac: Mac,
#[serde_as(as = "Base64")]
pub encrypt: Vec<u8>,
}
impl Default for MasterKey {
fn default() -> Self {
Self::from_key(Key::new())
}
}
impl MasterKey {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub(crate) fn from_key(key: Key) -> Self {
let (encrypt, k, r) = key.to_keys();
Self {
encrypt,
mac: Mac { k, r },
}
}
pub(crate) fn key(&self) -> Key {
Key::from_keys(&self.encrypt, &self.mac.k, &self.mac.r)
}
}
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)
}
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"))
}
}