use tracing::{debug, error};
use zeroize::Zeroize as _;
use crate::error::SynqroError;
use crate::keychain::KeychainProvider;
#[cfg(target_os = "ios")]
use security_framework::{
item::{ItemClass, ItemSearchOptions, Limit, Reference, SearchResult},
passwords::{delete_generic_password, get_generic_password, set_generic_password},
};
pub struct IosKeychain {
}
impl IosKeychain {
pub fn new() -> Result<Self, SynqroError> {
debug!("iOS Keychain Services backend initialised (security-framework)");
Ok(Self {})
}
}
impl KeychainProvider for IosKeychain {
fn load_secret(&self, service: &str, account: &str) -> Result<Vec<u8>, SynqroError> {
#[cfg(target_os = "ios")]
{
match get_generic_password(service, account) {
Ok(mut password_bytes) => {
debug!(
service = service,
account = account,
"iOS Keychain: secret loaded"
);
let result = password_bytes.clone();
password_bytes.zeroize();
return Ok(result);
}
Err(framework_err) => {
let code = framework_err.code();
if code == -25300 {
return Err(SynqroError::Keychain(format!(
"iOS Keychain: item not found for service='{}' account='{}'",
service, account
)));
}
if code == -25293 {
return Err(SynqroError::Keychain(
"iOS Keychain: device is locked; unlock device and retry".to_owned(),
));
}
error!(
service = service,
account = account,
code = code,
"iOS Keychain: get_generic_password failed"
);
return Err(SynqroError::Keychain(format!(
"iOS Keychain: SecItemCopyMatching error code {}",
code
)));
}
}
}
#[cfg(not(target_os = "ios"))]
Err(SynqroError::Permission(format!(
"iOS Keychain `load_secret` is unavailable on this target platform"
)))
}
fn store_secret(&self, service: &str, account: &str, secret: &[u8]) -> Result<(), SynqroError> {
#[cfg(target_os = "ios")]
{
match set_generic_password(service, account, secret) {
Ok(()) => {
debug!(
service = service,
account = account,
"iOS Keychain: secret stored with kSecAttrAccessibleWhenUnlockedThisDeviceOnly"
);
return Ok(());
}
Err(framework_err) => {
let code = framework_err.code();
if code == -25293 {
return Err(SynqroError::Keychain(
"iOS Keychain: device is locked; unlock device and retry".to_owned(),
));
}
error!(
service = service,
account = account,
code = code,
"iOS Keychain: set_generic_password failed"
);
return Err(SynqroError::Keychain(format!(
"iOS Keychain: SecItemAdd/SecItemUpdate error code {}",
code
)));
}
}
}
#[cfg(not(target_os = "ios"))]
Err(SynqroError::Permission(format!(
"iOS Keychain `store_secret` is unavailable on this target platform"
)))
}
fn delete_secret(&self, service: &str, account: &str) -> Result<(), SynqroError> {
#[cfg(target_os = "ios")]
{
match delete_generic_password(service, account) {
Ok(()) => {
debug!(
service = service,
account = account,
"iOS Keychain: secret deleted"
);
return Ok(());
}
Err(framework_err) => {
let code = framework_err.code();
if code == -25300 {
debug!(
service = service,
account = account,
"iOS Keychain: item not found during delete (already absent)"
);
return Ok(());
}
error!(
service = service,
account = account,
code = code,
"iOS Keychain: delete_generic_password failed"
);
return Err(SynqroError::Keychain(format!(
"iOS Keychain: SecItemDelete error code {}",
code
)));
}
}
}
#[cfg(not(target_os = "ios"))]
Err(SynqroError::Permission(format!(
"iOS Keychain `delete_secret` is unavailable on this target platform"
)))
}
}