use crate::error::EncryptError;
pub trait KeyProvider: Send + Sync {
fn get_key(&self) -> Result<&[u8], EncryptError>;
fn key32(&self) -> Result<&[u8; 32], EncryptError> {
let raw = self.get_key()?;
raw.try_into()
.map_err(|_| EncryptError::InvalidKeyLength { got: raw.len() })
}
}
#[derive(Clone)]
pub struct StaticKey(Vec<u8>);
impl StaticKey {
pub fn new(bytes: Vec<u8>) -> Self {
Self(bytes)
}
pub fn from_array(key: [u8; 32]) -> Self {
Self(key.to_vec())
}
}
impl core::fmt::Debug for StaticKey {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("StaticKey")
.field("len", &self.0.len())
.finish()
}
}
impl KeyProvider for StaticKey {
fn get_key(&self) -> Result<&[u8], EncryptError> {
if self.0.len() == 32 {
Ok(&self.0)
} else {
Err(EncryptError::InvalidKeyLength { got: self.0.len() })
}
}
}
pub struct KeyringKey {
label: String,
#[cfg(feature = "os-keyring")]
cached: std::sync::OnceLock<Vec<u8>>,
}
impl core::fmt::Debug for KeyringKey {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("KeyringKey")
.field("label", &self.label)
.field("key_material", &"[REDACTED]")
.finish()
}
}
impl Clone for KeyringKey {
fn clone(&self) -> Self {
Self::new(self.label.clone())
}
}
impl KeyringKey {
pub fn new(label: impl Into<String>) -> Self {
Self {
label: label.into(),
#[cfg(feature = "os-keyring")]
cached: std::sync::OnceLock::new(),
}
}
pub fn label(&self) -> &str {
&self.label
}
#[cfg(feature = "os-keyring")]
pub fn store_key(&self, key: &[u8; 32]) -> Result<(), EncryptError> {
let hex = encode_hex(key);
let entry = keyring_core::Entry::new("oxistore", &self.label).map_err(|e| {
EncryptError::KeyringUnavailable {
label: format!("{}: {}", self.label, e),
}
})?;
entry
.set_password(&hex)
.map_err(|e| EncryptError::KeyringUnavailable {
label: format!("{}: {}", self.label, e),
})
}
#[cfg(feature = "os-keyring")]
pub fn delete_entry(&self) -> Result<(), EncryptError> {
let entry = keyring_core::Entry::new("oxistore", &self.label).map_err(|e| {
EncryptError::KeyringUnavailable {
label: format!("{}: {}", self.label, e),
}
})?;
match entry.delete_credential() {
Ok(()) => Ok(()),
Err(keyring_core::Error::NoEntry) => Ok(()), Err(e) => Err(EncryptError::KeyringUnavailable {
label: format!("{}: {}", self.label, e),
}),
}
}
}
#[cfg(feature = "os-keyring")]
fn encode_hex(bytes: &[u8]) -> String {
const HEX: &[u8; 16] = b"0123456789abcdef";
let mut out = String::with_capacity(bytes.len() * 2);
for &b in bytes {
out.push(HEX[(b >> 4) as usize] as char);
out.push(HEX[(b & 0xf) as usize] as char);
}
out
}
#[cfg(feature = "os-keyring")]
fn decode_hex32(s: &str) -> Option<[u8; 32]> {
let s = s.trim();
if s.len() != 64 {
return None;
}
let mut out = [0u8; 32];
for (i, chunk) in s.as_bytes().chunks(2).enumerate() {
let hi = hex_nibble(chunk[0])?;
let lo = hex_nibble(chunk[1])?;
out[i] = (hi << 4) | lo;
}
Some(out)
}
#[cfg(feature = "os-keyring")]
fn hex_nibble(b: u8) -> Option<u8> {
match b {
b'0'..=b'9' => Some(b - b'0'),
b'a'..=b'f' => Some(b - b'a' + 10),
b'A'..=b'F' => Some(b - b'A' + 10),
_ => None,
}
}
impl KeyProvider for KeyringKey {
fn get_key(&self) -> Result<&[u8], EncryptError> {
#[cfg(feature = "os-keyring")]
{
if let Some(bytes) = self.cached.get() {
return Ok(bytes.as_slice());
}
let entry = keyring_core::Entry::new("oxistore", &self.label).map_err(|e| {
EncryptError::KeyringUnavailable {
label: format!("{}: {}", self.label, e),
}
})?;
let hex = entry
.get_password()
.map_err(|e| EncryptError::KeyringUnavailable {
label: format!("{}: {}", self.label, e),
})?;
let key32 = decode_hex32(&hex).ok_or_else(|| EncryptError::InvalidKeyLength {
got: hex.trim().len() / 2,
})?;
let stored = self.cached.get_or_init(|| key32.to_vec());
Ok(stored.as_slice())
}
#[cfg(not(feature = "os-keyring"))]
{
Err(EncryptError::KeyringUnavailable {
label: self.label.clone(),
})
}
}
}
#[cfg(feature = "os-keyring")]
impl Drop for KeyringKey {
fn drop(&mut self) {
if let Some(bytes) = self.cached.get_mut() {
for b in bytes.iter_mut() {
*b = 0;
}
}
}
}