use std::path::{Path, PathBuf};
use tenzro_keystore_unlock::{KeystoreUnlocker, Result as UnlockResult, UnlockError};
use zeroize::Zeroizing;
pub const DEFAULT_LABEL: &str = "tenzro-keystore-unlock";
pub struct SecureEnclaveUnlocker {
label: String,
ciphertext_path: PathBuf,
}
impl SecureEnclaveUnlocker {
pub fn new(label: impl Into<String>, ciphertext_path: impl Into<PathBuf>) -> Self {
Self {
label: label.into(),
ciphertext_path: ciphertext_path.into(),
}
}
pub fn under_data_dir(data_dir: impl AsRef<Path>) -> Self {
Self::new(
DEFAULT_LABEL,
data_dir.as_ref().join("keystore.pwd.enc"),
)
}
fn read_ciphertext(&self) -> Option<Vec<u8>> {
std::fs::read(&self.ciphertext_path).ok()
}
fn write_ciphertext(&self, bytes: &[u8]) -> UnlockResult<()> {
if let Some(parent) = self.ciphertext_path.parent() {
std::fs::create_dir_all(parent)
.map_err(|e| UnlockError::Backend(format!("create dir: {e}")))?;
}
std::fs::write(&self.ciphertext_path, bytes)
.map_err(|e| UnlockError::Backend(format!("write ciphertext: {e}")))
}
fn random_password() -> Zeroizing<String> {
use rand::RngCore;
use zeroize::Zeroize;
let mut raw = [0u8; 32];
rand::thread_rng().fill_bytes(&mut raw);
let pw = Zeroizing::new(hex::encode(raw));
raw.zeroize();
pw
}
}
impl KeystoreUnlocker for SecureEnclaveUnlocker {
fn unlock_password(&self) -> UnlockResult<Zeroizing<String>> {
match self.read_ciphertext() {
Some(ct) => {
let key = crate::open(&self.label).map_err(|e| match e {
crate::DeviceKeyError::NotFound(_) => UnlockError::Unavailable(format!(
"Secure Enclave key '{}' not found in the keychain — it may have been \
created on another machine or the login keychain was reset; the wallet \
needs re-creation",
self.label
)),
other => UnlockError::Backend(other.to_string()),
})?;
let pt = key
.unwrap_secret(&ct)
.map_err(|e| UnlockError::Backend(format!("unwrap: {e}")))?;
let s = String::from_utf8(pt.to_vec())
.map_err(|_| UnlockError::Backend("unwrapped password not utf-8".into()))?;
Ok(Zeroizing::new(s))
}
None => {
let key = crate::create(&self.label)
.map_err(|e| UnlockError::Backend(format!("create key: {e}")))?;
let password = Self::random_password();
let ct = key
.wrap_secret(password.as_bytes())
.map_err(|e| UnlockError::Backend(format!("wrap: {e}")))?;
self.write_ciphertext(&ct)?;
Ok(password)
}
}
}
}