Implement biometric-protected GPG unlock
This commit is contained in:
1 parent
3295761bcf
commit
873db91204
15 files changed
+2043
-59
No files matched your search
@@ -11,6 +11,15 @@ use std::sync::Arc;
|
||||
#[cfg(any(target_os = "ios", target_os = "macos"))]
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[cfg(any(target_os = "ios", target_os = "macos"))]
|
||||
use security_framework::{
|
||||
access_control::{ProtectionMode, SecAccessControl},
|
||||
passwords::{
|
||||
AccessControlOptions, PasswordOptions, delete_generic_password_options, generic_password,
|
||||
set_generic_password_options,
|
||||
},
|
||||
};
|
||||
|
||||
use keyring_core::{CredentialStore, Entry};
|
||||
|
||||
use super::{SecretLocator, SecretProtection, SecretStoreBackend, SecretStoreError};
|
||||
@@ -77,7 +86,7 @@ impl NativeSecretBackend {
|
||||
let (service, user) = locator.service_and_user();
|
||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||
{
|
||||
if protection == SecretProtection::RequireUserPresence {
|
||||
if protection != SecretProtection::DeviceUnlocked {
|
||||
return Err(SecretStoreError::UnsupportedProtection);
|
||||
}
|
||||
self.store.build(service, &user, None).map_err(map_error)
|
||||
@@ -101,6 +110,7 @@ impl NativeSecretBackend {
|
||||
.build(service, &user, modifiers.as_ref())
|
||||
.map_err(map_error)
|
||||
}
|
||||
SecretProtection::BiometryCurrentSet => Err(SecretStoreError::Unavailable),
|
||||
}
|
||||
}
|
||||
#[cfg(not(any(
|
||||
@@ -123,6 +133,10 @@ impl SecretStoreBackend for NativeSecretBackend {
|
||||
protection: SecretProtection,
|
||||
value: &[u8],
|
||||
) -> Result<(), SecretStoreError> {
|
||||
#[cfg(any(target_os = "ios", target_os = "macos"))]
|
||||
if protection == SecretProtection::BiometryCurrentSet {
|
||||
return strict_set(locator, value);
|
||||
}
|
||||
let entry = self.entry(locator, protection)?;
|
||||
match entry.get_secret() {
|
||||
Ok(existing) => {
|
||||
@@ -139,6 +153,12 @@ impl SecretStoreBackend for NativeSecretBackend {
|
||||
locator: &SecretLocator,
|
||||
protection: SecretProtection,
|
||||
) -> Result<SecretBytes, SecretStoreError> {
|
||||
#[cfg(any(target_os = "ios", target_os = "macos"))]
|
||||
if protection == SecretProtection::BiometryCurrentSet {
|
||||
return generic_password(strict_query(locator))
|
||||
.map(SecretBytes::new)
|
||||
.map_err(map_security_error);
|
||||
}
|
||||
self.entry(locator, protection)?
|
||||
.get_secret()
|
||||
.map(SecretBytes::new)
|
||||
@@ -151,6 +171,11 @@ impl SecretStoreBackend for NativeSecretBackend {
|
||||
protection: SecretProtection,
|
||||
value: &[u8],
|
||||
) -> Result<(), SecretStoreError> {
|
||||
#[cfg(any(target_os = "ios", target_os = "macos"))]
|
||||
if protection == SecretProtection::BiometryCurrentSet {
|
||||
delete_generic_password_options(strict_query(locator)).map_err(map_security_error)?;
|
||||
return strict_set(locator, value);
|
||||
}
|
||||
let entry = self.entry(locator, protection)?;
|
||||
let existing = entry.get_secret().map_err(map_error)?;
|
||||
drop(SecretBytes::new(existing));
|
||||
@@ -162,6 +187,11 @@ impl SecretStoreBackend for NativeSecretBackend {
|
||||
locator: &SecretLocator,
|
||||
protection: SecretProtection,
|
||||
) -> Result<(), SecretStoreError> {
|
||||
#[cfg(any(target_os = "ios", target_os = "macos"))]
|
||||
if protection == SecretProtection::BiometryCurrentSet {
|
||||
return delete_generic_password_options(strict_query(locator))
|
||||
.map_err(map_security_error);
|
||||
}
|
||||
self.entry(locator, protection)?
|
||||
.delete_credential()
|
||||
.map_err(map_error)
|
||||
@@ -175,6 +205,43 @@ fn presence_modifiers(protection: SecretProtection) -> Option<HashMap<&'static s
|
||||
SecretProtection::RequireUserPresence => {
|
||||
Some(HashMap::from([("access-policy", "require-user-presence")]))
|
||||
}
|
||||
SecretProtection::BiometryCurrentSet => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "ios", target_os = "macos"))]
|
||||
fn strict_query(locator: &SecretLocator) -> PasswordOptions {
|
||||
let (service, account) = locator.service_and_user();
|
||||
let mut options = PasswordOptions::new_generic_password(service, &account);
|
||||
options.use_protected_keychain();
|
||||
options
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "ios", target_os = "macos"))]
|
||||
fn strict_create_options(locator: &SecretLocator) -> Result<PasswordOptions, SecretStoreError> {
|
||||
let access = SecAccessControl::create_with_protection(
|
||||
Some(ProtectionMode::AccessibleWhenPasscodeSetThisDeviceOnly),
|
||||
AccessControlOptions::BIOMETRY_CURRENT_SET.bits(),
|
||||
)
|
||||
.map_err(map_security_error)?;
|
||||
let mut options = strict_query(locator);
|
||||
options.set_access_control(access);
|
||||
Ok(options)
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "ios", target_os = "macos"))]
|
||||
fn strict_set(locator: &SecretLocator, value: &[u8]) -> Result<(), SecretStoreError> {
|
||||
set_generic_password_options(value, strict_create_options(locator)?).map_err(map_security_error)
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "ios", target_os = "macos"))]
|
||||
fn map_security_error(error: security_framework::base::Error) -> SecretStoreError {
|
||||
match error.code() {
|
||||
-25300 => SecretStoreError::Missing,
|
||||
-128 => SecretStoreError::Cancelled,
|
||||
-25293 | -25308 => SecretStoreError::Denied,
|
||||
-50 => SecretStoreError::UnsupportedProtection,
|
||||
_ => SecretStoreError::Unavailable,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user