Share the platform-neutral Rust mobile contract (#91)
Dependency security audit / rustsec (push) Failing after 22s

This commit is contained in:
hugo committed 2026-10-08 13:25:09 +02:00
1 parent fcb36a0684
commit 180851087b
52 files changed
+8932 -4263

No files matched your search

+78 -21
View File
@@ -1,11 +1,17 @@
//! Shared mobile authentication state; Swift only supplies input and presents results.
//! Shared mobile authentication state; native frontends supply input and present results.
use std::{collections::BTreeMap, error::Error, fmt, sync::Mutex, time::Duration};
use std::{
collections::BTreeMap,
error::Error,
fmt,
sync::{Arc, Mutex},
time::Duration,
};
use crate::{
authentication::{
AuthenticationError, AuthenticationTimeout, NativeAuthenticationHandle,
NativeAuthenticationSession,
AuthenticationClock, AuthenticationError, AuthenticationHandle, AuthenticationSession,
AuthenticationTimeout, SystemAuthenticationClock,
},
config::{Config, ConfigError, GitRemote, MobileAppearance},
crypto::{CryptoError, KeyInfo, KeyStore, SecretProvider, SecretProviderError},
@@ -33,7 +39,8 @@ use crate::{
DirectoryPath, EncryptedEntry, EntryPath, Repository, RepositoryError, SecretBytes,
},
secret_store::{
NativeSecretStore, SecretCachePolicy, SecretProtectionPolicy, SecretStoreError,
NativeSecretBackend, SecretCachePolicy, SecretProtectionPolicy, SecretStore,
SecretStoreBackend, SecretStoreError,
},
write::{VaultWriter, WriteError},
};
@@ -279,7 +286,7 @@ impl MobileAuthenticationState {
}
struct ActiveMobileLease {
handle: NativeAuthenticationHandle,
handle: MobileAuthenticationHandle,
key: KeyInfo,
}
@@ -326,11 +333,17 @@ impl Drop for MobileRepositoryOperationGuard<'_> {
}
/// One process-wide mobile authentication lease shared by every tab and viewer.
type MobileAuthenticationSession =
AuthenticationSession<Arc<dyn SecretStoreBackend>, Arc<dyn AuthenticationClock>>;
type MobileAuthenticationHandle =
AuthenticationHandle<Arc<dyn SecretStoreBackend>, Arc<dyn AuthenticationClock>>;
pub struct MobileAuthentication {
config: Config,
repository: Repository,
keys: KeyStore,
session: NativeAuthenticationSession,
session: MobileAuthenticationSession,
backend: Arc<dyn SecretStoreBackend>,
status: Mutex<MobileAuthenticationStatus>,
totp_discovery: Mutex<()>,
}
@@ -344,13 +357,28 @@ impl MobileAuthentication {
error.to_string(),
)
})?;
let backend = Arc::new(NativeSecretBackend::new().map_err(preference_secret_error)?);
Self::with_platform(
config,
backend,
Arc::new(SystemAuthenticationClock::default()),
)
}
/// OS mechanics are injected; configuration, protection, leases and storage stay here.
pub fn with_platform(
config: Config,
backend: Arc<dyn SecretStoreBackend>,
clock: Arc<dyn AuthenticationClock>,
) -> Result<Self, MobileAuthenticationError> {
let repository = Repository::open(config.vault()).map_err(entry_error)?;
let keys = KeyStore::load(config.key_material()).map_err(key_error)?;
let session = NativeAuthenticationSession::system(
let session = MobileAuthenticationSession::with_clock(
Arc::clone(&backend),
SecretProtectionPolicy::current_biometry_for_openpgp(),
config.authentication_timeout(),
)
.map_err(MobileAuthenticationError::authentication)?;
clock,
);
let watch_snapshot =
WatchSnapshotSender::load(config.source().with_file_name("watch-snapshot.toml"));
Ok(Self {
@@ -371,6 +399,7 @@ impl MobileAuthentication {
repository,
keys,
session,
backend,
totp_discovery: Mutex::new(()),
})
}
@@ -599,7 +628,7 @@ impl MobileAuthentication {
sync_configured: remote.is_some(),
repository_title,
repository_url: remote.map_or_else(
|| "Stored and versioned on this iPhone".to_owned(),
|| "Stored and versioned on this device".to_owned(),
|remote| remote.url().to_string(),
),
server_title: remote
@@ -621,7 +650,10 @@ impl MobileAuthentication {
)
},
),
application_account: remote.map(application_account).transpose()?.flatten(),
application_account: remote
.map(|remote| application_account(remote, Arc::clone(&self.backend)))
.transpose()?
.flatten(),
default_key_title: key
.user_ids()
.first()
@@ -674,7 +706,7 @@ impl MobileAuthentication {
.find(|remote| remote.name().as_str() == "origin")
.or_else(|| self.config.git_remotes().first())
.ok_or_else(|| config_detail("No HTTPS Git remote is configured."))?;
let store = preference_secret_store()?;
let store = preference_secret_store(Arc::clone(&self.backend));
store.unlock().map_err(preference_secret_error)?;
let (server_id, application_id) = remote.https_credentials().ok_or_else(|| {
config_detail("The configured Git remote does not use HTTPS credentials.")
@@ -1276,6 +1308,26 @@ impl MobileAuthentication {
Ok(())
}
pub fn lifecycle(
&self,
event: crate::mobile_platform::MobileLifecycleEvent,
) -> Result<MobileAuthenticationState, MobileAuthenticationError> {
use crate::mobile_platform::MobileLifecycleEvent;
match event {
MobileLifecycleEvent::Foreground => {}
MobileLifecycleEvent::Background | MobileLifecycleEvent::DeviceLocked => {
self.manual_lock()?
}
MobileLifecycleEvent::UserActivity => {
if self.state()?.unlocked() {
self.touch_user_activity()?;
}
}
MobileLifecycleEvent::Cancel => self.cancel()?,
}
self.state()
}
pub fn cancel(&self) -> Result<(), MobileAuthenticationError> {
self.session
.cancel()
@@ -1319,7 +1371,7 @@ impl MobileAuthentication {
fn set_active(
&self,
handle: NativeAuthenticationHandle,
handle: MobileAuthenticationHandle,
key: KeyInfo,
) -> Result<(), MobileAuthenticationError> {
self.status()?.active = Some(ActiveMobileLease { handle, key });
@@ -1529,12 +1581,12 @@ fn repository_operation_conflict(
}
struct KeyOnlyProvider<'a> {
handle: NativeAuthenticationHandle,
handle: MobileAuthenticationHandle,
fingerprint: &'a str,
}
impl<'a> KeyOnlyProvider<'a> {
fn new(handle: NativeAuthenticationHandle, key: &'a KeyInfo) -> Self {
fn new(handle: MobileAuthenticationHandle, key: &'a KeyInfo) -> Self {
Self {
handle,
fingerprint: key.fingerprint().as_str(),
@@ -1567,19 +1619,24 @@ fn config_detail(detail: &str) -> MobileAuthenticationError {
)
}
fn preference_secret_store() -> Result<NativeSecretStore, MobileAuthenticationError> {
NativeSecretStore::system(
fn preference_secret_store(
backend: Arc<dyn SecretStoreBackend>,
) -> SecretStore<Arc<dyn SecretStoreBackend>> {
SecretStore::new(
backend,
SecretCachePolicy::Disabled,
SecretProtectionPolicy::device_unlocked(),
)
.map_err(preference_secret_error)
}
fn application_account(remote: &GitRemote) -> Result<Option<String>, MobileAuthenticationError> {
fn application_account(
remote: &GitRemote,
backend: Arc<dyn SecretStoreBackend>,
) -> Result<Option<String>, MobileAuthenticationError> {
let Some((server_id, application_id)) = remote.https_credentials() else {
return Ok(None);
};
let store = preference_secret_store()?;
let store = preference_secret_store(backend);
store.unlock().map_err(preference_secret_error)?;
let result = store.https_git_credential_account(server_id, application_id);
let lock_result = store.lock();