94 lines
3.2 KiB
Rust
94 lines
3.2 KiB
Rust
use blake3::Hasher;
|
|
use rand::RngCore;
|
|
use std::path::Path;
|
|
use tracing::{info, trace, warn};
|
|
|
|
pub struct MasterKey([u8; 32]);
|
|
|
|
impl MasterKey {
|
|
pub fn generate() -> Self {
|
|
let mut key = [0u8; 32];
|
|
rand::thread_rng().fill_bytes(&mut key);
|
|
Self(key)
|
|
}
|
|
|
|
pub fn from_hex(hex_str: &str) -> cgcx_core::Result<Self> {
|
|
let bytes = hex::decode(hex_str.trim())
|
|
.map_err(|e| cgcx_core::CgcxError::Crypto(format!("invalid master key hex: {}", e)))?;
|
|
if bytes.len() != 32 {
|
|
return Err(cgcx_core::CgcxError::Crypto(format!(
|
|
"master key must be 32 bytes (64 hex chars), got {} bytes",
|
|
bytes.len()
|
|
)));
|
|
}
|
|
let mut key = [0u8; 32];
|
|
key.copy_from_slice(&bytes);
|
|
Ok(Self(key))
|
|
}
|
|
|
|
pub fn to_hex(&self) -> String {
|
|
hex::encode(self.0)
|
|
}
|
|
|
|
pub fn as_bytes(&self) -> &[u8; 32] {
|
|
&self.0
|
|
}
|
|
|
|
pub fn fingerprint(&self) -> String {
|
|
let hash = Hasher::new().update(&self.0).finalize();
|
|
hex::encode(&hash.as_bytes()[..8])
|
|
}
|
|
|
|
pub fn load_from_env(var: &str) -> cgcx_core::Result<Self> {
|
|
sodiumoxide::init().map_err(|_| cgcx_core::CgcxError::Crypto("sodiumoxide init failed".into()))?;
|
|
match std::env::var(var) {
|
|
Ok(val) => {
|
|
let key = Self::from_hex(&val)?;
|
|
info!("Master key loaded from env var {}", var);
|
|
Ok(key)
|
|
}
|
|
Err(_) => {
|
|
let key = Self::generate();
|
|
warn!(
|
|
"Env var {} not set. A new master key has been generated.\n\
|
|
SAVE THIS KEY IMMEDIATELY (64 hex chars):\n{}\n\
|
|
Set it as {}=<key> to persist across restarts.",
|
|
var, key.to_hex(), var
|
|
);
|
|
Ok(key)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn load_from_file(path: &Path) -> cgcx_core::Result<Self> {
|
|
sodiumoxide::init().map_err(|_| cgcx_core::CgcxError::Crypto("sodiumoxide init failed".into()))?;
|
|
if path.exists() {
|
|
let val = std::fs::read_to_string(path)
|
|
.map_err(|e| cgcx_core::CgcxError::Crypto(format!("read key file: {}", e)))?;
|
|
let key = Self::from_hex(&val)?;
|
|
info!("Master key loaded from file {:?}", path);
|
|
Ok(key)
|
|
} else {
|
|
let key = Self::generate();
|
|
std::fs::write(path, key.to_hex())
|
|
.map_err(|e| cgcx_core::CgcxError::Crypto(format!("write key file: {}", e)))?;
|
|
#[cfg(unix)]
|
|
{
|
|
use std::os::unix::fs::PermissionsExt;
|
|
let mut perms = std::fs::metadata(path).unwrap().permissions();
|
|
perms.set_mode(0o600);
|
|
std::fs::set_permissions(path, perms).ok();
|
|
}
|
|
warn!("Generated new master key and wrote to {:?}", path);
|
|
Ok(key)
|
|
}
|
|
}
|
|
|
|
pub fn log_startup(&self, debug_log_keys: bool) {
|
|
info!("Storage master key loaded. fingerprint={}", self.fingerprint());
|
|
if debug_log_keys {
|
|
trace!("Master key full hex: {}", self.to_hex());
|
|
}
|
|
}
|
|
}
|