//! Android OS calls; all envelopes, policy and lifecycle remain in storage. use crate::{MobileSecretBackend, MobileSecretError, MobileSecretLocator, MobileSecretProtection}; use ironstorage::{ repository::SecretBytes, secret_store::{ SecretLocator, SecretProtection, SecretStoreBackend, SecretStoreError, android as os, }, }; use std::{fmt, sync::Arc}; #[derive(Clone, Copy, uniffi::Record)] pub struct MobileAndroidDeviceState { pub api: u32, pub locked: bool, pub secure: bool, pub strong_biometric: bool, pub strongbox: bool, } #[derive(Clone, Copy, uniffi::Enum)] pub enum MobileAndroidAuthorization { None, StrongBiometric, UserPresence, } #[derive(Clone, uniffi::Record)] pub struct MobileAndroidKeySpecification { pub alias: String, pub authorization: MobileAndroidAuthorization, pub unlocked_device: bool, pub invalidate_on_enrollment: bool, pub strongbox: bool, } #[derive(Clone, uniffi::Record)] pub struct MobileAndroidCiphertext { pub nonce: Vec, pub bytes: Vec, } #[derive(Clone, Copy, Debug, uniffi::Error)] pub enum MobileAndroidKeystoreError { MissingKey, Invalidated, Lockout, Locked, Denied, Cancelled, Corrupted, UnsupportedProtection, StrongboxUnavailable, AlreadyExists, Unavailable, } impl fmt::Display for MobileAndroidKeystoreError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("Android Keystore operation failed") } } impl std::error::Error for MobileAndroidKeystoreError {} #[uniffi::export(with_foreign)] pub trait MobileAndroidKeystore: Send + Sync { fn device_state(&self) -> Result; fn create_key( &self, specification: MobileAndroidKeySpecification, ) -> Result<(), MobileAndroidKeystoreError>; fn encrypt( &self, alias: String, authorization: MobileAndroidAuthorization, aad: Vec, value: Vec, ) -> Result; fn decrypt( &self, alias: String, authorization: MobileAndroidAuthorization, aad: Vec, value: MobileAndroidCiphertext, ) -> Result, MobileAndroidKeystoreError>; fn delete_key(&self, alias: String) -> Result<(), MobileAndroidKeystoreError>; fn aliases(&self) -> Result, MobileAndroidKeystoreError>; fn cancel_pending(&self); } pub(crate) struct KeystoreAdapter(pub Arc); impl From for MobileAndroidAuthorization { fn from(value: os::AndroidAuthorization) -> Self { match value { os::AndroidAuthorization::None => Self::None, os::AndroidAuthorization::StrongBiometric => Self::StrongBiometric, os::AndroidAuthorization::UserPresence => Self::UserPresence, } } } impl From for os::AndroidKeystoreError { fn from(value: MobileAndroidKeystoreError) -> Self { match value { MobileAndroidKeystoreError::MissingKey => Self::MissingKey, MobileAndroidKeystoreError::Invalidated => Self::Invalidated, MobileAndroidKeystoreError::Lockout => Self::Lockout, MobileAndroidKeystoreError::Locked => Self::Locked, MobileAndroidKeystoreError::Denied => Self::Denied, MobileAndroidKeystoreError::Cancelled => Self::Cancelled, MobileAndroidKeystoreError::Corrupted => Self::Corrupted, MobileAndroidKeystoreError::UnsupportedProtection => Self::UnsupportedProtection, MobileAndroidKeystoreError::StrongboxUnavailable => Self::StrongboxUnavailable, MobileAndroidKeystoreError::AlreadyExists => Self::AlreadyExists, MobileAndroidKeystoreError::Unavailable => Self::Unavailable, } } } impl os::AndroidKeystore for KeystoreAdapter { fn device_state(&self) -> Result { self.0 .device_state() .map(|s| os::AndroidDeviceState { api: s.api, locked: s.locked, secure: s.secure, strong_biometric: s.strong_biometric, strongbox: s.strongbox, }) .map_err(Into::into) } fn create_key(&self, s: &os::AndroidKeySpecification) -> Result<(), os::AndroidKeystoreError> { self.0 .create_key(MobileAndroidKeySpecification { alias: s.alias.clone(), authorization: s.authorization.into(), unlocked_device: s.unlocked_device, invalidate_on_enrollment: s.invalidate_on_enrollment, strongbox: s.strongbox, }) .map_err(Into::into) } fn encrypt( &self, alias: &str, authorization: os::AndroidAuthorization, aad: &[u8], value: &SecretBytes, ) -> Result { self.0 .encrypt( alias.into(), authorization.into(), aad.to_vec(), value.expose().to_vec(), ) .map(|v| os::AndroidCiphertext { nonce: v.nonce, bytes: v.bytes, }) .map_err(Into::into) } fn decrypt( &self, alias: &str, authorization: os::AndroidAuthorization, aad: &[u8], value: &os::AndroidCiphertext, ) -> Result { self.0 .decrypt( alias.into(), authorization.into(), aad.to_vec(), MobileAndroidCiphertext { nonce: value.nonce.clone(), bytes: value.bytes.clone(), }, ) .map(SecretBytes::new) .map_err(Into::into) } fn delete_key(&self, alias: &str) -> Result<(), os::AndroidKeystoreError> { self.0.delete_key(alias.into()).map_err(Into::into) } fn aliases(&self) -> Result, os::AndroidKeystoreError> { self.0.aliases().map_err(Into::into) } fn cancel_pending(&self) { self.0.cancel_pending(); } } pub(crate) struct RustSecretBackend(pub Arc); impl TryFrom for SecretLocator { type Error = MobileSecretError; fn try_from(value: MobileSecretLocator) -> Result { Ok(match value { MobileSecretLocator::ApplicationBinding => Self::ApplicationBinding, MobileSecretLocator::OpenPgpPassphrase { fingerprint } => { Self::OpenPgpPassphrase { fingerprint } } MobileSecretLocator::HttpsGitCredential { server_id, application_id, } => Self::HttpsGitCredential { server_id, application_id, }, MobileSecretLocator::SshKeyPassphrase { fingerprint } => Self::SshKeyPassphrase { fingerprint: ironstorage::config::SshFingerprint::parse(&fingerprint) .map_err(|_| MobileSecretError::Corrupted)?, }, }) } } impl From for SecretProtection { fn from(value: MobileSecretProtection) -> Self { match value { MobileSecretProtection::DeviceUnlocked => Self::DeviceUnlocked, MobileSecretProtection::RequireUserPresence => Self::RequireUserPresence, MobileSecretProtection::BiometryCurrentSet => Self::BiometryCurrentSet, } } } impl From for MobileSecretError { fn from(value: SecretStoreError) -> Self { match value { SecretStoreError::AlreadyExists => Self::AlreadyExists, SecretStoreError::Missing => Self::Missing, SecretStoreError::Locked => Self::Locked, SecretStoreError::Denied => Self::Denied, SecretStoreError::Cancelled => Self::Cancelled, SecretStoreError::Corrupted => Self::Corrupted, SecretStoreError::UnsupportedProtection => Self::UnsupportedProtection, SecretStoreError::Unavailable => Self::Unavailable, SecretStoreError::DeviceKeyMissing => Self::DeviceKeyMissing, SecretStoreError::Invalidated => Self::Invalidated, SecretStoreError::Lockout => Self::Lockout, _ => Self::Corrupted, } } } impl MobileSecretBackend for RustSecretBackend { fn create( &self, locator: MobileSecretLocator, protection: MobileSecretProtection, value: Vec, ) -> Result<(), MobileSecretError> { let secret = SecretBytes::new(value); self.0 .create(&locator.try_into()?, protection.into(), secret.expose()) .map_err(Into::into) } fn replace( &self, locator: MobileSecretLocator, protection: MobileSecretProtection, value: Vec, ) -> Result<(), MobileSecretError> { let secret = SecretBytes::new(value); self.0 .replace(&locator.try_into()?, protection.into(), secret.expose()) .map_err(Into::into) } fn retrieve( &self, locator: MobileSecretLocator, protection: MobileSecretProtection, ) -> Result, MobileSecretError> { self.0 .retrieve(&locator.try_into()?, protection.into()) .map(|v| v.expose().to_vec()) .map_err(Into::into) } fn delete( &self, locator: MobileSecretLocator, protection: MobileSecretProtection, ) -> Result<(), MobileSecretError> { self.0 .delete(&locator.try_into()?, protection.into()) .map_err(Into::into) } fn lock(&self) -> Result<(), MobileSecretError> { self.0.lock().map_err(Into::into) } fn unlock(&self) -> Result<(), MobileSecretError> { self.0.unlock().map_err(Into::into) } }