enigma-storage 0.0.1

Encrypted local storage for Enigma with mandatory at-rest encryption and cross-platform key vault providers.
Documentation
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)
    }
}