Implement biometric-protected GPG unlock

This commit is contained in:
2026-08-11 18:32:53 +02:00
parent 3295761bcf
commit 873db91204
15 changed files with 2043 additions and 59 deletions

View File

@@ -8,6 +8,11 @@ use std::{error::Error, fmt, sync::Arc};
use ironstorage::{
config::ConfigError,
mobile::{self, MobileShellState as StorageShellState, MobileTab as StorageTab},
mobile_authentication::{
MobileAuthenticationError as StorageAuthenticationError,
MobileAuthenticationErrorKind as StorageAuthenticationErrorKind,
MobileAuthenticationState as StorageAuthenticationState,
},
mobile_home::{
self, MobileHomeChangeKind as StorageHomeChangeKind,
MobileHomeChangeStatus as StorageHomeChangeStatus, MobileHomeError as StorageHomeError,
@@ -491,6 +496,135 @@ impl From<StoragePasswordError> for MobilePasswordFfiError {
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
pub enum MobileAuthenticationErrorKind {
PassphraseRequired,
InvalidPassphrase,
Cancelled,
BiometryUnavailable,
Configuration,
KeyMaterial,
Entry,
SecureStorage,
Expired,
}
impl From<StorageAuthenticationErrorKind> for MobileAuthenticationErrorKind {
fn from(kind: StorageAuthenticationErrorKind) -> Self {
match kind {
StorageAuthenticationErrorKind::PassphraseRequired => Self::PassphraseRequired,
StorageAuthenticationErrorKind::InvalidPassphrase => Self::InvalidPassphrase,
StorageAuthenticationErrorKind::Cancelled => Self::Cancelled,
StorageAuthenticationErrorKind::BiometryUnavailable => Self::BiometryUnavailable,
StorageAuthenticationErrorKind::Configuration => Self::Configuration,
StorageAuthenticationErrorKind::KeyMaterial => Self::KeyMaterial,
StorageAuthenticationErrorKind::Entry => Self::Entry,
StorageAuthenticationErrorKind::SecureStorage => Self::SecureStorage,
StorageAuthenticationErrorKind::Expired => Self::Expired,
}
}
}
#[derive(Clone, uniffi::Record)]
pub struct MobileAuthenticationState {
pub unlocked: bool,
pub biometric_unlock_enabled: bool,
pub remaining_seconds: u64,
}
impl From<StorageAuthenticationState> for MobileAuthenticationState {
fn from(state: StorageAuthenticationState) -> Self {
Self {
unlocked: state.unlocked(),
biometric_unlock_enabled: state.biometric_unlock_enabled(),
remaining_seconds: state.remaining_seconds(),
}
}
}
#[derive(Debug, uniffi::Error)]
pub enum MobileAuthenticationFfiError {
Failed {
kind: MobileAuthenticationErrorKind,
title: String,
detail: String,
},
}
impl fmt::Display for MobileAuthenticationFfiError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Failed { title, detail, .. } => write!(formatter, "{title}: {detail}"),
}
}
}
impl Error for MobileAuthenticationFfiError {}
impl From<StorageAuthenticationError> for MobileAuthenticationFfiError {
fn from(error: StorageAuthenticationError) -> Self {
Self::Failed {
kind: error.kind().into(),
title: error.title().to_owned(),
detail: error.detail().to_owned(),
}
}
}
#[derive(uniffi::Object)]
pub struct MobileAuthentication {
authentication: ironstorage::mobile_authentication::MobileAuthentication,
}
#[uniffi::export]
impl MobileAuthentication {
pub fn state(&self) -> Result<MobileAuthenticationState, MobileAuthenticationFfiError> {
self.authentication
.state()
.map(Into::into)
.map_err(Into::into)
}
pub fn unlock_entry(
&self,
path: String,
passphrase: Option<String>,
) -> Result<MobileAuthenticationState, MobileAuthenticationFfiError> {
self.authentication
.unlock_entry(
&path,
passphrase
.map(|value| ironstorage::repository::SecretBytes::new(value.into_bytes())),
)
.map(Into::into)
.map_err(Into::into)
}
pub fn set_biometric_unlock(
&self,
enabled: bool,
) -> Result<MobileAuthenticationState, MobileAuthenticationFfiError> {
self.authentication
.set_biometric_unlock(enabled)
.map(Into::into)
.map_err(Into::into)
}
pub fn touch_user_activity(&self) -> Result<(), MobileAuthenticationFfiError> {
self.authentication
.touch_user_activity()
.map_err(Into::into)
}
pub fn manual_lock(&self) -> Result<(), MobileAuthenticationFfiError> {
self.authentication.manual_lock().map_err(Into::into)
}
pub fn cancel(&self) -> Result<(), MobileAuthenticationFfiError> {
self.authentication.cancel().map_err(Into::into)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
pub enum MobileOnboardingPhase {
Validating,
@@ -711,6 +845,13 @@ pub fn mobile_password_page(
.map_err(Into::into)
}
#[uniffi::export]
pub fn mobile_authentication() -> Result<Arc<MobileAuthentication>, MobileAuthenticationFfiError> {
Ok(Arc::new(MobileAuthentication {
authentication: ironstorage::mobile_authentication::MobileAuthentication::load()?,
}))
}
#[uniffi::export]
pub fn mobile_onboarding_operation(
server_url: String,