feat(android): implement private Rust storage runtime (#90)
Dependency security audit / rustsec (push) Failing after 23s
Dependency security audit / rustsec (push) Failing after 23s
This commit is contained in:
1 parent
180851087b
commit
874c8139a4
36 files changed
+2848
-39
No files matched your search
@@ -3,6 +3,9 @@
|
||||
|
||||
//! Shared UniFFI contract for native phone presentation. Watch exports are opt-in.
|
||||
|
||||
mod private_storage;
|
||||
pub use private_storage::*;
|
||||
|
||||
use std::{
|
||||
error::Error,
|
||||
fmt,
|
||||
|
||||
@@ -70,6 +70,7 @@ pub fn mobile_platform_layout(
|
||||
|
||||
#[derive(Clone, uniffi::Enum)]
|
||||
pub enum MobileSecretLocator {
|
||||
ApplicationBinding,
|
||||
OpenPgpPassphrase {
|
||||
fingerprint: String,
|
||||
},
|
||||
@@ -85,6 +86,7 @@ pub enum MobileSecretLocator {
|
||||
impl From<&SecretLocator> for MobileSecretLocator {
|
||||
fn from(locator: &SecretLocator) -> Self {
|
||||
match locator {
|
||||
SecretLocator::ApplicationBinding => Self::ApplicationBinding,
|
||||
SecretLocator::OpenPgpPassphrase { fingerprint } => Self::OpenPgpPassphrase {
|
||||
fingerprint: fingerprint.clone(),
|
||||
},
|
||||
@@ -232,7 +234,7 @@ impl SecretStoreBackend for SecretBackendAdapter {
|
||||
pub trait MobileClock: Send + Sync {
|
||||
fn monotonic_milliseconds(&self) -> u64;
|
||||
}
|
||||
struct ClockAdapter(Arc<dyn MobileClock>);
|
||||
pub(crate) struct ClockAdapter(pub Arc<dyn MobileClock>);
|
||||
impl AuthenticationClock for ClockAdapter {
|
||||
fn now(&self) -> Duration {
|
||||
Duration::from_millis(self.0.monotonic_milliseconds())
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
//! Mechanical exports for the storage-owned Android private runtime.
|
||||
use crate::{
|
||||
MobileAuthentication, MobileShell,
|
||||
platform::{ClockAdapter, MobileClock, MobileSecretBackend, SecretBackendAdapter},
|
||||
};
|
||||
use ironstorage::mobile_private_storage::{
|
||||
PrivateDirectories, PrivateStorage, PrivateStorageError,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone, uniffi::Record)]
|
||||
pub struct MobilePrivateDirectories {
|
||||
pub files: String,
|
||||
pub no_backup: String,
|
||||
pub cache: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
||||
pub enum MobilePrivateStorageErrorKind {
|
||||
InvalidRoots,
|
||||
UnsafePath,
|
||||
MissingRoot,
|
||||
AccessDenied,
|
||||
ReadOnly,
|
||||
StorageFull,
|
||||
Busy,
|
||||
Io,
|
||||
CorruptLayout,
|
||||
MigrationRequired,
|
||||
CorruptConfiguration,
|
||||
ConfigurationMissingWithData,
|
||||
InterruptedSetup,
|
||||
RestoredWithoutKeystore,
|
||||
SecureStorageLocked,
|
||||
SecureStorageUnavailable,
|
||||
InvalidSecureBinding,
|
||||
}
|
||||
impl From<PrivateStorageError> for MobilePrivateStorageErrorKind {
|
||||
fn from(value: PrivateStorageError) -> Self {
|
||||
match value {
|
||||
PrivateStorageError::InvalidRoots => Self::InvalidRoots,
|
||||
PrivateStorageError::UnsafePath => Self::UnsafePath,
|
||||
PrivateStorageError::MissingRoot => Self::MissingRoot,
|
||||
PrivateStorageError::AccessDenied => Self::AccessDenied,
|
||||
PrivateStorageError::ReadOnly => Self::ReadOnly,
|
||||
PrivateStorageError::StorageFull => Self::StorageFull,
|
||||
PrivateStorageError::Busy => Self::Busy,
|
||||
PrivateStorageError::Io => Self::Io,
|
||||
PrivateStorageError::CorruptLayout => Self::CorruptLayout,
|
||||
PrivateStorageError::MigrationRequired => Self::MigrationRequired,
|
||||
PrivateStorageError::CorruptConfiguration => Self::CorruptConfiguration,
|
||||
PrivateStorageError::ConfigurationMissingWithData => Self::ConfigurationMissingWithData,
|
||||
PrivateStorageError::InterruptedSetup => Self::InterruptedSetup,
|
||||
PrivateStorageError::RestoredWithoutKeystore => Self::RestoredWithoutKeystore,
|
||||
PrivateStorageError::SecureStorageLocked => Self::SecureStorageLocked,
|
||||
PrivateStorageError::SecureStorageUnavailable => Self::SecureStorageUnavailable,
|
||||
PrivateStorageError::InvalidSecureBinding => Self::InvalidSecureBinding,
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Debug, uniffi::Error)]
|
||||
pub enum MobilePrivateStorageFfiError {
|
||||
Failed { kind: MobilePrivateStorageErrorKind },
|
||||
}
|
||||
impl std::fmt::Display for MobilePrivateStorageFfiError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str("Private storage operation failed")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for MobilePrivateStorageFfiError {}
|
||||
impl From<PrivateStorageError> for MobilePrivateStorageFfiError {
|
||||
fn from(value: PrivateStorageError) -> Self {
|
||||
Self::Failed { kind: value.into() }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, uniffi::Record)]
|
||||
pub struct MobilePrivateStorageStatus {
|
||||
pub version: u32,
|
||||
pub configured: bool,
|
||||
pub recovered_temporary_files: bool,
|
||||
pub recovered_catalog: bool,
|
||||
pub secure_binding_present: bool,
|
||||
pub recovery: Option<MobilePrivateStorageErrorKind>,
|
||||
}
|
||||
#[derive(Clone, uniffi::Record)]
|
||||
pub struct MobilePrivateStorageLayout {
|
||||
pub data: String,
|
||||
pub configuration: String,
|
||||
pub vault: String,
|
||||
pub keys: String,
|
||||
pub catalog: String,
|
||||
pub scratch: String,
|
||||
pub cache: String,
|
||||
}
|
||||
|
||||
#[derive(uniffi::Object)]
|
||||
pub struct MobilePrivateRuntime {
|
||||
storage: PrivateStorage,
|
||||
}
|
||||
#[uniffi::export]
|
||||
impl MobilePrivateRuntime {
|
||||
pub fn activate(&self) -> Result<(), MobilePrivateStorageFfiError> {
|
||||
self.storage.activate().map_err(Into::into)
|
||||
}
|
||||
pub fn shell(&self) -> MobileShell {
|
||||
self.storage.shell().into()
|
||||
}
|
||||
pub fn status(&self) -> MobilePrivateStorageStatus {
|
||||
let status = self.storage.status();
|
||||
MobilePrivateStorageStatus {
|
||||
version: status.version,
|
||||
configured: status.configured,
|
||||
recovered_temporary_files: status.recovered_temporary_files,
|
||||
recovered_catalog: status.recovered_catalog,
|
||||
secure_binding_present: status.secure_binding_present,
|
||||
recovery: status.recovery.map(Into::into),
|
||||
}
|
||||
}
|
||||
pub fn layout(&self) -> MobilePrivateStorageLayout {
|
||||
let root = self.storage.root();
|
||||
let path = |name| root.join(name).to_string_lossy().into_owned();
|
||||
MobilePrivateStorageLayout {
|
||||
data: root.to_string_lossy().into_owned(),
|
||||
configuration: path("config.toml"),
|
||||
vault: path("vault"),
|
||||
keys: path("keys"),
|
||||
catalog: path("totp-catalog.toml"),
|
||||
scratch: path("scratch"),
|
||||
cache: self.storage.cache_path().to_string_lossy().into_owned(),
|
||||
}
|
||||
}
|
||||
pub fn verify_secure_storage(
|
||||
&self,
|
||||
backend: Arc<dyn MobileSecretBackend>,
|
||||
) -> Result<(), MobilePrivateStorageFfiError> {
|
||||
self.storage
|
||||
.verify_secure_storage(&SecretBackendAdapter(backend))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
pub fn clear_disposable_cache(&self) -> Result<(), MobilePrivateStorageFfiError> {
|
||||
self.storage.clear_disposable_cache().map_err(Into::into)
|
||||
}
|
||||
pub fn authentication(
|
||||
&self,
|
||||
backend: Arc<dyn MobileSecretBackend>,
|
||||
clock: Arc<dyn MobileClock>,
|
||||
) -> Result<Arc<MobileAuthentication>, crate::MobileAuthenticationFfiError> {
|
||||
self.storage
|
||||
.verify_secure_storage(&SecretBackendAdapter(Arc::clone(&backend)))
|
||||
.map_err(private_auth_error)?;
|
||||
let config = self
|
||||
.storage
|
||||
.configuration()
|
||||
.map_err(private_auth_error)?
|
||||
.ok_or_else(|| private_auth_error(PrivateStorageError::ConfigurationMissingWithData))?;
|
||||
let authentication =
|
||||
ironstorage::mobile_authentication::MobileAuthentication::with_platform(
|
||||
config,
|
||||
Arc::new(SecretBackendAdapter(backend)),
|
||||
Arc::new(ClockAdapter(clock)),
|
||||
)?;
|
||||
Ok(Arc::new(MobileAuthentication {
|
||||
authentication: Arc::new(authentication),
|
||||
}))
|
||||
}
|
||||
}
|
||||
fn private_auth_error(error: PrivateStorageError) -> crate::MobileAuthenticationFfiError {
|
||||
crate::MobileAuthenticationFfiError::Failed {
|
||||
kind: crate::MobileAuthenticationErrorKind::Configuration,
|
||||
title: "Private Storage Recovery Required".into(),
|
||||
detail: error.detail().into(),
|
||||
}
|
||||
}
|
||||
#[uniffi::export]
|
||||
pub fn open_mobile_private_runtime(
|
||||
directories: MobilePrivateDirectories,
|
||||
) -> Result<Arc<MobilePrivateRuntime>, MobilePrivateStorageFfiError> {
|
||||
let storage = PrivateStorage::open(PrivateDirectories {
|
||||
files: directories.files.into(),
|
||||
no_backup: directories.no_backup.into(),
|
||||
cache: directories.cache.into(),
|
||||
})?;
|
||||
Ok(Arc::new(MobilePrivateRuntime { storage }))
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
pub fn mobile_private_storage_failure(error: MobilePrivateStorageErrorKind) -> MobileShell {
|
||||
let value = match error {
|
||||
MobilePrivateStorageErrorKind::InvalidRoots => PrivateStorageError::InvalidRoots,
|
||||
MobilePrivateStorageErrorKind::UnsafePath => PrivateStorageError::UnsafePath,
|
||||
MobilePrivateStorageErrorKind::MissingRoot => PrivateStorageError::MissingRoot,
|
||||
MobilePrivateStorageErrorKind::AccessDenied => PrivateStorageError::AccessDenied,
|
||||
MobilePrivateStorageErrorKind::ReadOnly => PrivateStorageError::ReadOnly,
|
||||
MobilePrivateStorageErrorKind::StorageFull => PrivateStorageError::StorageFull,
|
||||
MobilePrivateStorageErrorKind::Busy => PrivateStorageError::Busy,
|
||||
MobilePrivateStorageErrorKind::Io => PrivateStorageError::Io,
|
||||
MobilePrivateStorageErrorKind::CorruptLayout => PrivateStorageError::CorruptLayout,
|
||||
MobilePrivateStorageErrorKind::MigrationRequired => PrivateStorageError::MigrationRequired,
|
||||
MobilePrivateStorageErrorKind::CorruptConfiguration => {
|
||||
PrivateStorageError::CorruptConfiguration
|
||||
}
|
||||
MobilePrivateStorageErrorKind::ConfigurationMissingWithData => {
|
||||
PrivateStorageError::ConfigurationMissingWithData
|
||||
}
|
||||
MobilePrivateStorageErrorKind::InterruptedSetup => PrivateStorageError::InterruptedSetup,
|
||||
MobilePrivateStorageErrorKind::RestoredWithoutKeystore => {
|
||||
PrivateStorageError::RestoredWithoutKeystore
|
||||
}
|
||||
MobilePrivateStorageErrorKind::SecureStorageLocked => {
|
||||
PrivateStorageError::SecureStorageLocked
|
||||
}
|
||||
MobilePrivateStorageErrorKind::SecureStorageUnavailable => {
|
||||
PrivateStorageError::SecureStorageUnavailable
|
||||
}
|
||||
MobilePrivateStorageErrorKind::InvalidSecureBinding => {
|
||||
PrivateStorageError::InvalidSecureBinding
|
||||
}
|
||||
};
|
||||
ironstorage::mobile::MobileShell::storage_failure(value.detail().into()).into()
|
||||
}
|
||||
Reference in new issue
Block a user