use std::collections::HashMap;
use std::path::{Path, PathBuf};
use base64::engine::general_purpose::STANDARD;
use base64::Engine;
use blake3::Hasher;
use rand::RngCore;
use secret_service::{EncryptionType, SecretService};
use tokio::runtime::{Handle, Runtime};
use crate::error::{EnigmaStorageError, Result};
use crate::key_provider::{KeyProvider, MasterKey};
pub struct LinuxSecretServiceKeyProvider {
root: PathBuf,
namespace: String,
}
impl LinuxSecretServiceKeyProvider {
pub fn new<P: AsRef<Path>>(root: P, namespace: &str) -> Self {
LinuxSecretServiceKeyProvider {
root: root.as_ref().to_path_buf(),
namespace: namespace.to_owned(),
}
}
fn attributes(&self) -> HashMap<String, String> {
let mut attrs = HashMap::new();
attrs.insert("app".into(), "enigma-storage".into());
let mut hasher = Hasher::new();
hasher.update(b"enigma:ss:id:v1");
hasher.update(self.root.to_string_lossy().as_bytes());
hasher.update(self.namespace.as_bytes());
let id = hasher.finalize().to_hex().to_string();
attrs.insert("id".into(), id);
attrs
}
fn run_async<F, T>(&self, fut: F) -> Result<T>
where
F: std::future::Future<Output = Result<T>>,
{
if let Ok(handle) = Handle::try_current() {
handle.block_on(fut)
} else {
let rt = Runtime::new().map_err(|e| EnigmaStorageError::KeyProviderError(e.to_string()))?;
rt.block_on(fut)
}
}
}
impl KeyProvider for LinuxSecretServiceKeyProvider {
fn get_or_create_master_key(&self) -> Result<MasterKey> {
self.run_async(async {
let ss = SecretService::new(EncryptionType::Dh)
.await
.map_err(map_ss_error)?;
let collection = ss
.get_default_collection()
.await
.map_err(map_ss_error)?;
let attrs = self.attributes();
let items = collection
.search_items(attrs.clone())
.await
.map_err(map_ss_error)?;
if let Some(item) = items.first() {
let secret = item.get_secret().await.map_err(map_ss_error)?;
return decode_secret(&secret);
}
let mut key_bytes = [0u8; 32];
rand::thread_rng().fill_bytes(&mut key_bytes);
let encoded = STANDARD.encode(key_bytes);
collection
.create_item(
"enigma-storage master key",
attrs,
encoded.as_bytes(),
true,
"text/plain",
)
.await
.map_err(map_ss_error)?;
Ok(MasterKey::new(key_bytes))
})
}
fn get_master_key(&self) -> Result<MasterKey> {
self.run_async(async {
let ss = SecretService::new(EncryptionType::Dh)
.await
.map_err(map_ss_error)?;
let collection = ss
.get_default_collection()
.await
.map_err(map_ss_error)?;
let attrs = self.attributes();
let items = collection
.search_items(attrs.clone())
.await
.map_err(map_ss_error)?;
if let Some(item) = items.first() {
let secret = item.get_secret().await.map_err(map_ss_error)?;
return decode_secret(&secret);
}
Err(EnigmaStorageError::KeyProviderError(
"secret not found in Secret Service".into(),
))
})
}
}
fn decode_secret(secret: &[u8]) -> Result<MasterKey> {
let decoded = STANDARD
.decode(secret)
.map_err(|e| EnigmaStorageError::KeyProviderError(e.to_string()))?;
if decoded.len() != 32 {
return Err(EnigmaStorageError::InvalidKey);
}
let mut key = [0u8; 32];
key.copy_from_slice(&decoded);
Ok(MasterKey::new(key))
}
fn map_ss_error(err: secret_service::Error) -> EnigmaStorageError {
let msg = err.to_string();
if msg.to_lowercase().contains("secret service") || msg.to_lowercase().contains("dbus") {
EnigmaStorageError::PlatformUnavailable("Secret Service unavailable; use FileSealedKeyProvider or ForeignKeyProvider".into())
} else if msg.to_lowercase().contains("locked") {
EnigmaStorageError::PermissionDenied
} else {
EnigmaStorageError::KeyProviderError(msg)
}
}