#![allow(dead_code, unused_imports, unused_qualifications, unreachable_patterns)]
use super::ffi;
use super::keychain::{self, KeychainConfig};
use super::lacontext;
use crate::internal::core::traits::{EnclaveKeyManager, EnclaveSigner};
use crate::internal::core::types::{validate_label, AccessPolicy, KeyType, PresenceMode};
use crate::internal::core::{Error, Result};
fn should_evict_lacontext(e: &Error) -> bool {
!matches!(
e,
Error::KeychainAuthDenied { .. }
| Error::KeychainInteractionRequired { .. }
| Error::KeychainNoWindowServer { .. }
| Error::UserCancelled { .. }
)
}
#[allow(unsafe_code)]
pub fn touch_id_available() -> bool {
unsafe { ffi::enclaveapp_se_touch_id_available() == 1 }
}
#[derive(Debug)]
pub struct SecureEnclaveSigner {
config: KeychainConfig,
}
impl SecureEnclaveSigner {
pub fn new(app_name: &str) -> Self {
SecureEnclaveSigner {
config: KeychainConfig::new(app_name),
}
}
pub fn with_keys_dir(app_name: &str, keys_dir: std::path::PathBuf) -> Self {
SecureEnclaveSigner {
config: KeychainConfig::with_keys_dir(app_name, keys_dir),
}
}
pub fn with_config(config: KeychainConfig) -> Self {
SecureEnclaveSigner { config }
}
#[allow(unsafe_code)] fn sign_inner(&self, label: &str, data: &[u8], lacontext_token: u64) -> Result<Vec<u8>> {
validate_label(label)?;
let data_rep = keychain::load_handle_with_context(&self.config, label, lacontext_token)?;
let mut sig = vec![0_u8; 128]; let mut sig_len: i32 = 128;
let rc = unsafe {
ffi::enclaveapp_se_sign(
data_rep.as_ptr(),
data_rep.len() as i32,
data.as_ptr(),
data.len() as i32,
sig.as_mut_ptr(),
&mut sig_len,
lacontext_token,
)
};
if rc != 0 {
return Err(match rc {
16 => Error::UserCancelled {
label: label.to_string(),
},
_ => {
let detail = match keychain::last_bridge_error() {
Some(msg) => format!("FFI returned error code {rc}: {msg}"),
None => format!("FFI returned error code {rc}"),
};
Error::SignFailed { detail }
}
});
}
sig.truncate(sig_len as usize);
Ok(sig)
}
}
impl EnclaveKeyManager for SecureEnclaveSigner {
fn generate(&self, label: &str, key_type: KeyType, policy: AccessPolicy) -> Result<Vec<u8>> {
validate_label(label)?;
if key_type != KeyType::Signing {
return Err(Error::KeyOperation {
operation: "generate".into(),
detail: "SecureEnclaveSigner only supports signing keys".into(),
});
}
keychain::generate_and_save_key(&self.config, label, key_type, policy)
}
fn public_key(&self, label: &str) -> Result<Vec<u8>> {
validate_label(label)?;
keychain::load_pub_key(&self.config, label, KeyType::Signing)
}
fn list_keys(&self) -> Result<Vec<String>> {
keychain::list_labels(&self.config)
}
fn delete_key(&self, label: &str) -> Result<()> {
validate_label(label)?;
keychain::delete_key(&self.config, label)
}
fn rename_key(&self, old_label: &str, new_label: &str) -> Result<()> {
keychain::rename_key(&self.config, old_label, new_label)
}
fn is_available(&self) -> bool {
keychain::is_available()
}
}
impl EnclaveSigner for SecureEnclaveSigner {
fn sign(&self, label: &str, data: &[u8]) -> Result<Vec<u8>> {
self.sign_inner(label, data, 0)
}
fn sign_with_presence(
&self,
label: &str,
data: &[u8],
mode: PresenceMode,
cache_ttl_secs: u64,
reason: &str,
) -> Result<Vec<u8>> {
match mode {
PresenceMode::Cached => {
let token =
lacontext::acquire(&self.config.app_name, label, cache_ttl_secs, reason)
.map(|h| h.token())
.unwrap_or(0);
let result = self.sign_inner(label, data, token);
if let Err(ref e) = result {
if should_evict_lacontext(e) {
lacontext::evict(&self.config.app_name, label);
}
}
result
}
PresenceMode::Strict => {
let handle = lacontext::create_once(reason);
let token = handle.as_ref().map(|h| h.token()).unwrap_or(0);
self.sign_inner(label, data, token)
}
PresenceMode::None => {
let handle = lacontext::create_once(reason);
let token = handle.as_ref().map(|h| h.token()).unwrap_or(0);
self.sign_inner(label, data, token)
}
}
}
fn evict_wrapping_key_cache(&self, label: &str) {
crate::internal::apple::keychain_wrap::cache_evict_for(&self.config.app_name, label);
}
}