use std::path::Path;
use crate::Result;
use crate::error::PersistError;
use crate::types::SecretKey;
const KEY_LEN: usize = 32;
pub fn load_or_generate_key(path: &Path) -> Result<SecretKey> {
match load_key(path) {
Ok(key) => Ok(key),
Err(e) if is_not_found(&e) => generate_new_key(path),
Err(e) => Err(e),
}
}
pub fn generate_new_key(path: &Path) -> Result<SecretKey> {
let bytes: [u8; KEY_LEN] = rand::random();
let key = SecretKey::from_bytes(&bytes);
match save_key_exclusive(path, &key) {
Ok(()) => Ok(key),
Err(e) if is_already_exists(&e) => load_key(path),
Err(e) => Err(e),
}
}
fn load_key(path: &Path) -> Result<SecretKey> {
let bytes = std::fs::read(path).map_err(|e| PersistError::PathIo {
op: "read key file",
path: path.to_path_buf(),
source: e,
})?;
if bytes.len() != KEY_LEN {
return Err(PersistError::InvalidKeyLength {
expected: KEY_LEN,
actual: bytes.len(),
}
.into());
}
let arr: [u8; KEY_LEN] =
bytes
.try_into()
.map_err(|v: Vec<u8>| PersistError::InvalidKeyLength {
expected: KEY_LEN,
actual: v.len(),
})?;
Ok(SecretKey::from_bytes(&arr))
}
fn save_key_exclusive(path: &Path, key: &SecretKey) -> Result<()> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).map_err(|e| PersistError::PathIo {
op: "create key directory",
path: parent.to_path_buf(),
source: e,
})?;
}
#[cfg(unix)]
{
use std::fs::OpenOptions;
use std::io::Write;
use std::os::unix::fs::OpenOptionsExt;
let mut file = OpenOptions::new()
.write(true)
.create_new(true)
.mode(0o600)
.open(path)
.map_err(|e| PersistError::PathIo {
op: "create key file",
path: path.to_path_buf(),
source: e,
})?;
file.write_all(&key.to_bytes())
.map_err(|e| PersistError::PathIo {
op: "write key file",
path: path.to_path_buf(),
source: e,
})?;
}
#[cfg(not(unix))]
{
use std::fs::OpenOptions;
use std::io::Write;
let mut file = OpenOptions::new()
.write(true)
.create_new(true)
.open(path)
.map_err(|e| PersistError::PathIo {
op: "create key file",
path: path.to_path_buf(),
source: e,
})?;
file.write_all(&key.to_bytes())
.map_err(|e| PersistError::PathIo {
op: "write key file",
path: path.to_path_buf(),
source: e,
})?;
}
Ok(())
}
fn is_not_found(err: &crate::error::SculkError) -> bool {
matches!(
err,
crate::error::SculkError::Persist(PersistError::PathIo { source, .. })
if source.kind() == std::io::ErrorKind::NotFound
)
}
fn is_already_exists(err: &crate::error::SculkError) -> bool {
matches!(
err,
crate::error::SculkError::Persist(PersistError::PathIo { source, .. })
if source.kind() == std::io::ErrorKind::AlreadyExists
)
}