android-native-keyring-store 1.0.0

Android CredentialStore for keyring-core
Documentation
use std::sync::Arc;

use keyring_core::{Credential, api::CredentialApi};

use crate::crypto::{decrypt, encrypt};

use super::vault::AtomicVault;

/// The Cred struct is public, so you can read the cred's ID and specifiers.
///
/// Every Cred also points back to its vault, which is needed for its operation,
/// but that is not a public member since vaults aren't public.
///
/// Every Cred operation works by locking its containing vault and then asking
/// the vault to perform the operation. This means that all credential operations
/// are serialized and never interfere with one another.
pub struct Cred {
    vault: AtomicVault,
    pub id: String,
    pub specifiers: (String, String),
}

impl std::fmt::Debug for Cred {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("AndroidCredential")
            .field("vault", &self.vault)
            .field("key", &self.id)
            .field("specifiers", &self.specifiers)
            .finish()
    }
}

impl Cred {
    /// Create a new credential object in the given vault with the given ID for the given
    /// service and user.
    ///
    /// No validity checking is done, that's assumed to be done by the caller.
    pub fn new_specifier(vault: AtomicVault, id: &str, service: &str, user: &str) -> Self {
        Self {
            vault,
            id: id.to_owned(),
            specifiers: (service.to_owned(), user.to_owned()),
        }
    }
}

impl CredentialApi for Cred {
    fn set_secret(&self, secret: &[u8]) -> keyring_core::Result<()> {
        let vault = self
            .vault
            .lock()
            .expect("Vault lock poisoned: report a bug!");
        vault.with_key_and_file(|env, key, file| {
            let ciphertext = encrypt(env, key, secret)?;
            let edit = file.edit(env)?;
            edit.put_binary(env, &self.id, &ciphertext)?.commit(env)?;
            Ok(())
        })?;
        Ok(())
    }

    fn get_secret(&self) -> keyring_core::Result<Vec<u8>> {
        let vault = self
            .vault
            .lock()
            .expect("Vault lock poisoned: report a bug!");
        let result = vault.with_key_and_file(|env, key, file| {
            let ciphertext = file.get_binary(env, &self.id)?;
            if let Some(data) = ciphertext {
                log::debug!("Found secret for id {:?}", self.id);
                let plaintext = decrypt(env, key, data)?;
                Ok(Some(plaintext))
            } else {
                log::debug!("No secret found for id {:?}", self.id);
                Ok(None)
            }
        })?;
        match result {
            Some(secret) => Ok(secret),
            None => Err(keyring_core::Error::NoEntry),
        }
    }

    fn delete_credential(&self) -> keyring_core::Result<()> {
        let vault = self
            .vault
            .lock()
            .expect("Vault lock poisoned: report a bug!");
        vault.with_env(|env| {
            let file = vault.get_file(env)?;
            if !file.contains(env, &self.id)? {
                log::debug!("No credential to delete for id {:?}", self.id);
                return Err(keyring_core::Error::NoEntry.into());
            }
            log::debug!("Deleting credential for id {:?}", self.id);
            let editor = file.edit(env)?;
            editor.remove(env, &self.id)?.commit(env)?;
            Ok(())
        })?;
        Ok(())
    }

    fn get_credential(&self) -> keyring_core::Result<Option<Arc<Credential>>> {
        let vault = self
            .vault
            .lock()
            .expect("Vault lock poisoned: report a bug!");
        vault.with_env(|env| {
            let file = vault.get_file(env)?;
            if !file.contains(env, &self.id)? {
                log::debug!("No credential for id {:?}", self.id);
                Err(keyring_core::Error::NoEntry)?;
            }
            log::debug!("Found credential for id {:?}", self.id);
            Ok(())
        })?;
        Ok(None)
    }

    fn get_specifiers(&self) -> Option<(String, String)> {
        Some(self.specifiers.clone())
    }

    fn as_any(&self) -> &dyn std::any::Any {
        self
    }

    fn debug_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        std::fmt::Debug::fmt(self, f)
    }
}