Share the platform-neutral Rust mobile contract (#91)
Dependency security audit / rustsec (push) Failing after 22s
Dependency security audit / rustsec (push) Failing after 22s
This commit is contained in:
1 parent
fcb36a0684
commit
180851087b
52 files changed
+8932
-4263
No files matched your search
@@ -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();
|
||||
|
||||
Reference in new issue
Block a user