#![allow(dead_code, unused_imports, unused_qualifications, unreachable_patterns)]
use super::ffi;
use crate::internal::core::metadata::{self, KeyMeta};
use crate::internal::core::types::{validate_label, KeyType};
use crate::internal::core::{Error, Result};
use std::path::PathBuf;
use zeroize::Zeroizing;
const SE_ERR_BUFFER_TOO_SMALL: i32 = 4;
#[allow(unsafe_code)]
pub(crate) fn last_bridge_error() -> Option<String> {
let mut buf = vec![0_u8; 1024];
let mut buf_len: i32 = buf.len() as i32;
let rc = unsafe { ffi::enclaveapp_se_last_error(buf.as_mut_ptr(), &mut buf_len) };
if rc != 0 || buf_len <= 0 {
return None;
}
let len = buf_len as usize;
buf.truncate(len);
String::from_utf8(buf).ok().filter(|s| !s.is_empty())
}
#[derive(Debug)]
pub struct KeychainConfig {
pub app_name: String,
pub keys_dir_override: Option<PathBuf>,
pub wrapping_key_user_presence: bool,
pub wrapping_key_cache_ttl: std::time::Duration,
pub keychain_access_group: Option<String>,
}
impl KeychainConfig {
pub fn new(app_name: &str) -> Self {
KeychainConfig {
app_name: crate::internal::apple::signing::ensure_safe_app_name(app_name),
keys_dir_override: None,
wrapping_key_user_presence: false,
wrapping_key_cache_ttl: std::time::Duration::ZERO,
keychain_access_group: None,
}
}
pub fn with_keys_dir(app_name: &str, keys_dir: PathBuf) -> Self {
KeychainConfig {
app_name: crate::internal::apple::signing::ensure_safe_app_name(app_name),
keys_dir_override: Some(keys_dir),
wrapping_key_user_presence: false,
wrapping_key_cache_ttl: std::time::Duration::ZERO,
keychain_access_group: None,
}
}
#[must_use]
pub fn with_user_presence(mut self, enabled: bool) -> Self {
self.wrapping_key_user_presence = enabled;
self
}
#[must_use]
pub fn with_cache_ttl(mut self, ttl: std::time::Duration) -> Self {
self.wrapping_key_cache_ttl = ttl;
self
}
#[must_use]
pub fn with_access_group(mut self, group: impl Into<String>) -> Self {
self.keychain_access_group = Some(group.into());
self
}
pub fn keys_dir(&self) -> PathBuf {
self.keys_dir_override
.clone()
.unwrap_or_else(|| metadata::keys_dir(&self.app_name))
}
}
#[allow(unsafe_code)] pub fn is_available() -> bool {
unsafe { ffi::enclaveapp_se_available() == 1 }
}
#[allow(unsafe_code)] pub fn generate_key(key_type: KeyType, auth_policy: i32) -> Result<(Vec<u8>, Vec<u8>)> {
if !is_available() {
return Err(Error::NotAvailable);
}
match key_type {
KeyType::Signing => {
generate_key_with_retry(|pub_key, pub_key_len, data_rep, data_rep_len| unsafe {
ffi::enclaveapp_se_generate_signing_key(
pub_key,
pub_key_len,
data_rep,
data_rep_len,
auth_policy,
)
})
}
KeyType::Encryption => {
generate_key_with_retry(|pub_key, pub_key_len, data_rep, data_rep_len| unsafe {
ffi::enclaveapp_se_generate_encryption_key(
pub_key,
pub_key_len,
data_rep,
data_rep_len,
auth_policy,
)
})
}
}
}
fn generate_key_with_retry<F>(mut generate_ffi: F) -> Result<(Vec<u8>, Vec<u8>)>
where
F: FnMut(*mut u8, *mut i32, *mut u8, *mut i32) -> i32,
{
const MAX_RESIZE_RETRIES: usize = 4;
const UNCOMPRESSED_P256_PUBKEY_LEN: usize = 65;
let mut data_rep_capacity = 1024_usize;
for _ in 0..MAX_RESIZE_RETRIES {
let mut pub_key = vec![0_u8; UNCOMPRESSED_P256_PUBKEY_LEN];
let mut pub_key_len: i32 = UNCOMPRESSED_P256_PUBKEY_LEN as i32;
let mut data_rep = vec![0_u8; data_rep_capacity];
let mut data_rep_len: i32 = data_rep_capacity as i32;
let rc = generate_ffi(
pub_key.as_mut_ptr(),
&mut pub_key_len,
data_rep.as_mut_ptr(),
&mut data_rep_len,
);
if rc == SE_ERR_BUFFER_TOO_SMALL {
let returned = usize::try_from(data_rep_len).unwrap_or(0);
if returned > data_rep_capacity {
data_rep_capacity = returned;
continue;
}
return Err(Error::GenerateFailed {
detail: format!(
"FFI reported SE_ERR_BUFFER_TOO_SMALL but did not grow data_rep_len \
(sent {data_rep_capacity} bytes, got back {returned}) — \
Swift bridge contract violation"
),
});
}
if rc != 0 {
let detail = match last_bridge_error() {
Some(msg) => format!("FFI returned error code {rc}: {msg}"),
None => format!("FFI returned error code {rc}"),
};
return Err(Error::GenerateFailed { detail });
}
let pub_key_len_usize = usize::try_from(pub_key_len).unwrap_or(0);
if pub_key_len_usize > UNCOMPRESSED_P256_PUBKEY_LEN {
return Err(Error::GenerateFailed {
detail: format!(
"FFI reported pub_key_len = {pub_key_len_usize} but the buffer is \
fixed at {UNCOMPRESSED_P256_PUBKEY_LEN} bytes — Swift bridge contract violation"
),
});
}
pub_key.truncate(pub_key_len_usize);
let data_rep_len_usize = usize::try_from(data_rep_len).unwrap_or(0);
data_rep.truncate(data_rep_len_usize);
return Ok((pub_key, data_rep));
}
Err(Error::GenerateFailed {
detail: format!(
"Swift bridge repeatedly returned SE_ERR_BUFFER_TOO_SMALL after {MAX_RESIZE_RETRIES} \
retries — contract violation"
),
})
}
pub fn generate_and_save_key(
config: &KeychainConfig,
label: &str,
key_type: KeyType,
policy: crate::internal::core::AccessPolicy,
) -> Result<Vec<u8>> {
validate_label(label)?;
let dir = config.keys_dir();
metadata::ensure_dir(&dir)?;
let _lock = metadata::DirLock::acquire(&dir)?;
prepare_label_for_save(&dir, label)?;
let (pub_key, data_rep) = generate_key(key_type, policy.as_ffi_value())?;
let app_name = config.app_name.clone();
let label_owned = label.to_string();
let wrapped_blob = match crate::internal::apple::keychain_wrap::generate_and_wrap(
&app_name,
label,
&data_rep,
config.wrapping_key_user_presence,
config.keychain_access_group.as_deref(),
) {
Ok(blob) => blob,
Err(error) => {
drop(delete_key_from_data_rep(&data_rep));
return Err(error);
}
};
let app_name_for_cleanup = app_name.clone();
let access_group_for_cleanup = config.keychain_access_group.clone();
let data_rep_for_post_persist_rollback = data_rep.clone();
let cleanup = move || {
drop(crate::internal::apple::keychain_wrap::keychain_delete(
&app_name_for_cleanup,
&label_owned,
access_group_for_cleanup.as_deref(),
));
delete_key_from_data_rep(&data_rep)
};
persist_saved_key_material(
&dir,
label,
key_type,
policy,
&wrapped_blob,
&pub_key,
cleanup,
)?;
let hmac_key_opt = crate::internal::apple::meta_hmac::load_or_create(&config.app_name)
.ok()
.flatten();
if let Some(hk) = hmac_key_opt {
let meta_path = dir.join(format!("{label}.meta"));
let meta_bytes = match std::fs::read(&meta_path) {
Ok(b) => b,
Err(e) => {
rollback_after_persist(
&dir,
label,
&config.app_name,
config.keychain_access_group.as_deref(),
&data_rep_for_post_persist_rollback,
);
return Err(Error::KeyOperation {
operation: "post_persist_meta_read".into(),
detail: format!("read {}: {e}", meta_path.display()),
});
}
};
let tag = metadata::compute_meta_hmac_bytes(hk.as_slice(), &meta_bytes);
if let Err(e) = crate::internal::apple::meta_tag::store(&config.app_name, label, &tag) {
rollback_after_persist(
&dir,
label,
&config.app_name,
config.keychain_access_group.as_deref(),
&data_rep_for_post_persist_rollback,
);
return Err(e);
}
let hmac_path = dir.join(format!("{label}.meta.hmac"));
let mut tag_hex = String::with_capacity(64);
for byte in tag {
tag_hex.push_str(&format!("{byte:02x}"));
}
if let Err(e) = metadata::atomic_write(&hmac_path, tag_hex.as_bytes()) {
tracing::warn!(
label = label,
error = %e,
"post-persist .meta.hmac sidecar write failed (best-effort cache; \
verify will rebuild from keychain tag)"
);
}
} else {
tracing::warn!(
label = label,
"meta-HMAC key unavailable at keygen; key persisted without integrity tag. \
Run `<app> migrate-meta` once the Keychain is reachable."
);
}
Ok(pub_key)
}
fn ensure_meta_integrity(app_name: &str, label: &str, dir: &std::path::Path) -> Result<()> {
let meta_path = dir.join(format!("{label}.meta"));
if !meta_path.exists() {
return Ok(());
}
let stored_tag = match crate::internal::apple::meta_tag::load(app_name, label) {
Ok(Some(t)) => t,
Ok(None) => {
return legacy_meta_error(app_name, label);
}
Err(_) => {
return Ok(());
}
};
let hmac_key = match crate::internal::apple::meta_hmac::load_existing(app_name) {
Ok(Some(k)) => k,
Ok(None) | Err(_) => return Ok(()),
};
let meta_bytes = std::fs::read(&meta_path).map_err(|e| Error::KeyOperation {
operation: "meta_tag_verify".into(),
detail: format!("read {}: {e}", meta_path.display()),
})?;
let actual = metadata::compute_meta_hmac_bytes(hmac_key.as_slice(), &meta_bytes);
let mut diff: u8 = 0;
for i in 0..stored_tag.len() {
diff |= stored_tag[i] ^ actual[i];
}
if diff == 0 {
return Ok(());
}
Err(Error::KeyOperation {
operation: "meta_tag_verify".into(),
detail: format!(
"key '{label}': metadata integrity check failed. The on-disk meta \
does not match the keychain-stored tag — meta may have been \
tampered with. Refusing to proceed. Regenerate the key to restore \
a known-good state."
),
})
}
fn legacy_meta_error(app_name: &str, label: &str) -> Result<()> {
let marker_set =
crate::internal::apple::meta_migration_marker::is_set(app_name).unwrap_or(false);
if marker_set {
return Err(Error::KeyOperation {
operation: "meta_tag_legacy_post_migration".into(),
detail: format!(
"key '{label}' has no integrity tag, but `{app_name} migrate-meta` \
has already completed on this install. This is a strong tamper \
signal — legitimate operation should not produce a missing tag \
after the marker is set. Recommended: regenerate the affected \
key with `{app_name} keygen`. Do NOT run migrate-meta again \
unless you can independently explain why this key's tag is \
missing (e.g., manual restore from a backup of an unrelated \
machine), in which case pass \
`--force-rerun-i-understand` to override."
),
});
}
Err(Error::KeyOperation {
operation: "meta_tag_legacy".into(),
detail: format!(
"key '{label}' has no integrity tag. This is the one-time \
migration required by upgrading to a build that introduces meta \
integrity tags, and is not something future upgrades will repeat. \
Before migrating, verify the key's current policy looks correct: \
`{app_name} inspect {label}`. To migrate: `{app_name} \
migrate-meta`."
),
})
}
fn rollback_after_persist(
dir: &std::path::Path,
label: &str,
app_name: &str,
access_group: Option<&str>,
data_rep: &[u8],
) {
if let Err(e) = crate::internal::apple::meta_tag::delete(app_name, label) {
tracing::warn!(label = label, error = %e, "rollback: meta_tag::delete failed");
}
if let Err(e) = cleanup_persisted_key_material(dir, label) {
tracing::warn!(label = label, error = %e, "rollback: file cleanup failed");
}
if let Err(e) =
crate::internal::apple::keychain_wrap::keychain_delete(app_name, label, access_group)
{
tracing::warn!(label = label, error = %e, "rollback: wrapping-key delete failed");
}
if let Err(e) = delete_key_from_data_rep(data_rep) {
tracing::warn!(label = label, error = %e, "rollback: SE key delete failed");
}
}
#[allow(unsafe_code)] pub fn public_key_from_data_rep(key_type: KeyType, data_rep: &[u8]) -> Result<Vec<u8>> {
let mut pub_key = vec![0_u8; 65];
let mut pub_key_len: i32 = 65;
let rc = match key_type {
KeyType::Signing => unsafe {
ffi::enclaveapp_se_signing_public_key(
data_rep.as_ptr(),
data_rep.len() as i32,
pub_key.as_mut_ptr(),
&mut pub_key_len,
)
},
KeyType::Encryption => unsafe {
ffi::enclaveapp_se_encryption_public_key(
data_rep.as_ptr(),
data_rep.len() as i32,
pub_key.as_mut_ptr(),
&mut pub_key_len,
)
},
};
if rc != 0 {
let detail = match last_bridge_error() {
Some(msg) => format!("FFI returned error code {rc}: {msg}"),
None => format!("FFI returned error code {rc}"),
};
return Err(Error::KeyOperation {
operation: "public_key".into(),
detail,
});
}
pub_key.truncate(pub_key_len as usize);
Ok(pub_key)
}
#[cfg_attr(not(test), allow(dead_code))]
fn save_key(
config: &KeychainConfig,
label: &str,
key_type: KeyType,
policy: crate::internal::core::AccessPolicy,
data_rep: &[u8],
pub_key: &[u8],
) -> Result<()> {
validate_label(label)?;
let dir = config.keys_dir();
metadata::ensure_dir(&dir)?;
let _lock = metadata::DirLock::acquire(&dir)?;
prepare_label_for_save(&dir, label)?;
persist_saved_key_material(&dir, label, key_type, policy, data_rep, pub_key, || {
delete_key_from_data_rep(data_rep)
})
}
pub fn rename_key(config: &KeychainConfig, old_label: &str, new_label: &str) -> Result<()> {
validate_label(old_label)?;
validate_label(new_label)?;
if old_label == new_label {
return Ok(());
}
let dir = config.keys_dir();
let _lock = metadata::DirLock::acquire(&dir)?;
let old_handle = dir.join(format!("{old_label}.handle"));
let old_meta = dir.join(format!("{old_label}.meta"));
if !old_handle.exists() && !old_meta.exists() {
return Err(Error::KeyNotFound {
label: old_label.to_string(),
});
}
let hmac_key_opt = crate::internal::apple::meta_hmac::load_or_create(&config.app_name)
.ok()
.flatten();
metadata::rename_key_files(
&dir,
old_label,
new_label,
hmac_key_opt.as_ref().map(|z| z.as_slice()),
)?;
if let Err(error) = crate::internal::apple::keychain_wrap::relabel_wrapping_key(
&config.app_name,
old_label,
new_label,
config.wrapping_key_user_presence,
config.keychain_access_group.as_deref(),
) {
let rollback = metadata::rename_key_files(
&dir,
new_label,
old_label,
hmac_key_opt.as_ref().map(|z| z.as_slice()),
);
if let Err(rollback_err) = rollback {
tracing::error!(
"rename_key: relabel_wrapping_key failed ({error}) and disk rollback ALSO failed \
({rollback_err}); key material may be in an inconsistent state"
);
}
return Err(error);
}
if let Some(hk) = hmac_key_opt {
let new_meta_path = dir.join(format!("{new_label}.meta"));
if let Ok(meta_bytes) = std::fs::read(&new_meta_path) {
let tag = metadata::compute_meta_hmac_bytes(hk.as_slice(), &meta_bytes);
if let Err(e) =
crate::internal::apple::meta_tag::store(&config.app_name, new_label, &tag)
{
tracing::error!(
old_label = old_label,
new_label = new_label,
error = %e,
"rename_key: meta_tag store under new label failed; \
run `<app> migrate-meta` to restore the integrity tag"
);
return Err(e);
}
}
if let Err(e) = crate::internal::apple::meta_tag::delete(&config.app_name, old_label) {
tracing::warn!(
old_label = old_label,
error = %e,
"rename_key: stale meta-tag for old label could not be deleted (harmless orphan)"
);
}
}
Ok(())
}
pub fn load_handle(config: &KeychainConfig, label: &str) -> Result<Zeroizing<Vec<u8>>> {
load_handle_with_context(config, label, 0)
}
pub fn load_handle_with_context(
config: &KeychainConfig,
label: &str,
lacontext_token: u64,
) -> Result<Zeroizing<Vec<u8>>> {
validate_label(label)?;
let dir = config.keys_dir();
ensure_meta_integrity(&config.app_name, label, &dir)?;
let path = dir.join(format!("{label}.handle"));
if !path.exists() {
return Err(Error::KeyNotFound {
label: label.to_string(),
});
}
let contents = metadata::read_no_follow(&path)?;
if !crate::internal::apple::keychain_wrap::is_wrapped_handle(&contents) {
tracing::debug!(
label = label,
"loaded legacy plaintext SE handle; re-save to upgrade to wrapped format"
);
return Ok(Zeroizing::new(contents));
}
match crate::internal::apple::keychain_wrap::decrypt_with_cached_key(
&config.app_name,
label,
&contents,
config.wrapping_key_cache_ttl,
config.keychain_access_group.as_deref(),
lacontext_token,
Some(config.wrapping_key_user_presence),
)? {
Some(plaintext) => Ok(Zeroizing::new(plaintext)),
None => Err(Error::KeyOperation {
operation: "load_handle".into(),
detail: format!(
"wrapped handle for label `{label}` is missing its keychain wrapping key; \
the keychain entry may have been deleted or the user denied access"
),
}),
}
}
pub fn load_pub_key(config: &KeychainConfig, label: &str, key_type: KeyType) -> Result<Vec<u8>> {
validate_label(label)?;
let dir = config.keys_dir();
ensure_meta_integrity(&config.app_name, label, &dir)?;
if let Ok(pub_key) = metadata::load_pub_key(&dir, label) {
if crate::internal::core::types::validate_p256_point(&pub_key).is_ok() {
return Ok(pub_key);
}
}
let data_rep = load_handle(config, label)?;
let pub_key = public_key_from_data_rep(key_type, &data_rep)?;
metadata::sync_pub_key(&dir, label, &pub_key)
}
pub fn list_labels(config: &KeychainConfig) -> Result<Vec<String>> {
metadata::list_labels_for_extensions(&config.keys_dir(), &["meta", "handle"])
}
pub fn delete_key(config: &KeychainConfig, label: &str) -> Result<()> {
validate_label(label)?;
let dir = config.keys_dir();
let handle_path = dir.join(format!("{label}.handle"));
let key_exists =
dir.exists() && (handle_path.exists() || metadata::key_files_exist(&dir, label)?);
if !key_exists {
return Err(Error::KeyNotFound {
label: label.to_string(),
});
}
let _lock = metadata::DirLock::acquire(&dir)?;
let result = match load_handle(config, label) {
Ok(data_rep) => {
delete_key_from_data_rep(&data_rep).map_err(|error| Error::KeyOperation {
operation: "delete_key".into(),
detail: format!("delete Secure Enclave key: {error}"),
})?;
metadata::delete_key_files(&dir, label)
}
Err(Error::KeyNotFound { .. }) => metadata::delete_key_files(&dir, label),
Err(error) => Err(Error::KeyOperation {
operation: "delete_key".into(),
detail: format!(
"failed to read Secure Enclave handle; preserving local key material for retry: {error}"
),
}),
};
if let Err(error) = crate::internal::apple::keychain_wrap::keychain_delete(
&config.app_name,
label,
config.keychain_access_group.as_deref(),
) {
tracing::warn!(
label = label,
"keychain_delete failed during delete_key (harmless if the handle is already gone): {error}"
);
}
if let Err(error) = crate::internal::apple::meta_tag::delete(&config.app_name, label) {
tracing::warn!(
label = label,
"meta_tag::delete failed during delete_key (harmless orphan if the meta is already gone): {error}"
);
}
result
}
#[allow(unsafe_code)] fn delete_key_from_data_rep(data_rep: &[u8]) -> Result<()> {
let rc = unsafe { ffi::enclaveapp_se_delete_key(data_rep.as_ptr(), data_rep.len() as i32) };
if rc == 0 {
Ok(())
} else {
let detail = match last_bridge_error() {
Some(msg) => format!("FFI returned error code {rc}: {msg}"),
None => format!("FFI returned error code {rc}"),
};
Err(Error::KeyOperation {
operation: "delete_key".into(),
detail,
})
}
}
fn persist_saved_key_material<F>(
dir: &std::path::Path,
label: &str,
key_type: KeyType,
policy: crate::internal::core::AccessPolicy,
data_rep: &[u8],
pub_key: &[u8],
cleanup_created_key: F,
) -> Result<()>
where
F: FnOnce() -> Result<()>,
{
let handle_path = dir.join(format!("{label}.handle"));
if let Err(error) = metadata::atomic_write(&handle_path, data_rep) {
return Err(with_cleanup_context(
"persist handle",
error,
dir,
label,
cleanup_created_key,
));
}
if let Err(error) = metadata::restrict_file_permissions(&handle_path) {
return Err(with_cleanup_context(
"persist handle permissions",
error,
dir,
label,
cleanup_created_key,
));
}
if let Err(error) = metadata::save_pub_key(dir, label, pub_key) {
return Err(with_cleanup_context(
"persist public key cache",
error,
dir,
label,
cleanup_created_key,
));
}
let meta = KeyMeta::new(label, key_type, policy);
if let Err(error) = metadata::save_meta(dir, label, &meta) {
return Err(with_cleanup_context(
"persist metadata",
error,
dir,
label,
cleanup_created_key,
));
}
Ok(())
}
fn prepare_label_for_save(dir: &std::path::Path, label: &str) -> Result<()> {
let handle_path = dir.join(format!("{label}.handle"));
let other_files_exist = metadata::key_files_exist(dir, label)?;
if !handle_path.exists() && !other_files_exist {
return Ok(());
}
if !handle_path.exists() {
return metadata::delete_key_files(dir, label);
}
let data_rep = metadata::read_no_follow(&handle_path).map_err(|error| Error::KeyOperation {
operation: "prepare_label_for_save".into(),
detail: format!("failed to read existing Secure Enclave handle: {error}"),
})?;
if public_key_from_data_rep(KeyType::Signing, &data_rep).is_ok()
|| public_key_from_data_rep(KeyType::Encryption, &data_rep).is_ok()
{
return Err(Error::DuplicateLabel {
label: label.to_string(),
});
}
delete_key_from_data_rep(&data_rep).map_err(|error| Error::KeyOperation {
operation: "prepare_label_for_save".into(),
detail: format!(
"failed to delete existing Secure Enclave key before reusing label: {error}"
),
})?;
metadata::delete_key_files(dir, label)
}
fn cleanup_persisted_key_material(dir: &std::path::Path, label: &str) -> Result<()> {
for extension in ["handle", "pub", "meta", "meta.hmac", "ssh.pub"] {
let path = dir.join(format!("{label}.{extension}"));
if path.is_file() {
std::fs::remove_file(path)?;
}
}
Ok(())
}
fn with_cleanup_context<F>(
operation: &str,
error: Error,
dir: &std::path::Path,
label: &str,
cleanup_created_key: F,
) -> Error
where
F: FnOnce() -> Result<()>,
{
let mut cleanup_failures = Vec::new();
if let Err(cleanup_error) = cleanup_persisted_key_material(dir, label) {
cleanup_failures.push(format!("remove persisted key files: {cleanup_error}"));
}
if let Err(cleanup_error) = cleanup_created_key() {
cleanup_failures.push(format!("delete Secure Enclave key: {cleanup_error}"));
}
if cleanup_failures.is_empty() {
error
} else {
Error::GenerateFailed {
detail: format!(
"{operation} failed: {error}; cleanup failed: {}",
cleanup_failures.join("; ")
),
}
}
}
#[cfg(test)]
#[allow(clippy::panic, clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn keychain_config_new_sets_app_name() {
let config = KeychainConfig::new("sshenc");
assert_eq!(config.app_name, "sshenc-unsigned");
assert!(config.keys_dir_override.is_none());
}
#[test]
fn keychain_config_new_different_app_name() {
let config = KeychainConfig::new("awsenc");
assert_eq!(config.app_name, "awsenc-unsigned");
}
#[test]
#[allow(unsafe_code)]
fn generate_key_with_retry_resizes_data_rep_buffer() {
let mut attempts = 0;
let (pub_key, data_rep) =
generate_key_with_retry(|pub_key, pub_key_len, data_rep, data_rep_len| {
attempts += 1;
if attempts == 1 {
unsafe {
*data_rep_len = 2048;
}
return SE_ERR_BUFFER_TOO_SMALL;
}
unsafe {
*pub_key_len = 65;
*data_rep_len = 2048;
std::ptr::write_bytes(pub_key, 0x04, 65);
std::ptr::write_bytes(data_rep, 0xAB, 2048);
}
0
})
.unwrap();
assert_eq!(attempts, 2);
assert_eq!(pub_key.len(), 65);
assert_eq!(data_rep.len(), 2048);
assert!(data_rep.iter().all(|byte| *byte == 0xAB));
}
#[test]
fn keychain_config_with_keys_dir_overrides_path() {
let custom = PathBuf::from("/tmp/custom-keys");
let config = KeychainConfig::with_keys_dir("sshenc", custom.clone());
assert_eq!(config.app_name, "sshenc-unsigned");
assert_eq!(config.keys_dir_override, Some(custom));
}
#[test]
fn keys_dir_returns_default_when_no_override() {
let config = KeychainConfig::new("test-app");
let dir = config.keys_dir();
let expected = metadata::keys_dir("test-app-unsigned");
assert_eq!(dir, expected);
}
#[test]
fn keys_dir_returns_override_when_set() {
let custom = PathBuf::from("/tmp/my-custom-keys");
let config = KeychainConfig::with_keys_dir("test-app", custom.clone());
assert_eq!(config.keys_dir(), custom);
}
#[test]
fn delete_missing_key_in_missing_dir_returns_key_not_found() {
let dir =
std::env::temp_dir().join(format!("enclaveapp-apple-missing-{}", std::process::id()));
drop(std::fs::remove_dir_all(&dir));
let config = KeychainConfig::with_keys_dir("test-app", dir);
let err = delete_key(&config, "ghost").unwrap_err();
match err {
Error::KeyNotFound { label } => assert_eq!(label, "ghost"),
other => panic!("expected KeyNotFound, got {other}"),
}
}
#[test]
fn keychain_operations_reject_invalid_labels() {
let dir =
std::env::temp_dir().join(format!("enclaveapp-apple-invalid-{}", std::process::id()));
drop(std::fs::remove_dir_all(&dir));
let config = KeychainConfig::with_keys_dir("test-app", dir);
let err = load_handle(&config, "../escape").unwrap_err();
assert!(matches!(err, Error::InvalidLabel { .. }));
let err = load_pub_key(&config, "../escape", KeyType::Signing).unwrap_err();
assert!(matches!(err, Error::InvalidLabel { .. }));
let err = delete_key(&config, "../escape").unwrap_err();
assert!(matches!(err, Error::InvalidLabel { .. }));
}
#[test]
fn save_key_rejects_invalid_labels() {
let dir = std::env::temp_dir().join(format!(
"enclaveapp-apple-save-invalid-{}",
std::process::id()
));
drop(std::fs::remove_dir_all(&dir));
let config = KeychainConfig::with_keys_dir("test-app", dir);
let err = save_key(
&config,
"../escape",
KeyType::Signing,
crate::internal::core::AccessPolicy::None,
b"handle",
b"pub",
)
.unwrap_err();
assert!(matches!(err, Error::InvalidLabel { .. }));
}
#[test]
fn save_key_recovers_stale_metadata_artifacts() {
let dir = std::env::temp_dir().join(format!(
"enclaveapp-apple-duplicate-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
drop(std::fs::remove_dir_all(&dir));
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(dir.join("existing.pub"), b"pub").unwrap();
let config = KeychainConfig::with_keys_dir("test-app", dir.clone());
save_key(
&config,
"existing",
KeyType::Signing,
crate::internal::core::AccessPolicy::None,
b"handle",
&[0x04; 65],
)
.unwrap();
assert!(dir.join("existing.handle").exists());
assert!(dir.join("existing.pub").exists());
assert!(dir.join("existing.meta").exists());
std::fs::remove_dir_all(dir).unwrap();
}
#[test]
fn persist_saved_key_material_cleans_up_files_and_invokes_key_cleanup() {
let dir = std::env::temp_dir().join(format!(
"enclaveapp-apple-persist-cleanup-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
drop(std::fs::remove_dir_all(&dir));
std::fs::create_dir_all(&dir).unwrap();
std::fs::create_dir(dir.join("work.meta")).unwrap();
let mut cleaned_up = false;
let err = persist_saved_key_material(
&dir,
"work",
KeyType::Signing,
crate::internal::core::AccessPolicy::None,
b"handle",
&[0x04; 65],
|| {
cleaned_up = true;
Ok(())
},
)
.unwrap_err();
assert!(matches!(err, Error::Io(_) | Error::GenerateFailed { .. }));
assert!(
cleaned_up,
"generated Secure Enclave key should be cleaned up"
);
assert!(!dir.join("work.handle").exists());
assert!(!dir.join("work.pub").exists());
assert!(dir.join("work.meta").is_dir());
std::fs::remove_dir_all(dir).unwrap();
}
#[test]
#[ignore = "writes .meta + hits real Keychain via load_handle; CI hang"]
fn delete_key_preserves_files_when_handle_cannot_be_read() {
let dir = std::env::temp_dir().join(format!(
"enclaveapp-apple-delete-corrupt-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
drop(std::fs::remove_dir_all(&dir));
std::fs::create_dir_all(&dir).unwrap();
let handle_path = dir.join("work.handle");
std::fs::write(&handle_path, b"handle").unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&handle_path, std::fs::Permissions::from_mode(0o000)).unwrap();
}
metadata::save_pub_key(&dir, "work", &[0x04; 65]).unwrap();
metadata::save_meta(
&dir,
"work",
&KeyMeta::new(
"work",
KeyType::Signing,
crate::internal::core::AccessPolicy::None,
),
)
.unwrap();
let config = KeychainConfig::with_keys_dir("test-app", dir.clone());
let err = delete_key(&config, "work").unwrap_err();
assert!(matches!(err, Error::KeyOperation { .. }));
assert!(dir.join("work.handle").exists());
assert!(dir.join("work.pub").exists());
assert!(dir.join("work.meta").exists());
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&handle_path, std::fs::Permissions::from_mode(0o600)).ok();
}
std::fs::remove_dir_all(dir).unwrap();
}
#[test]
fn list_labels_includes_handle_without_metadata() {
let dir = std::env::temp_dir().join(format!(
"enclaveapp-apple-list-live-handle-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
drop(std::fs::remove_dir_all(&dir));
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(dir.join("alpha.handle"), b"handle").unwrap();
std::fs::write(dir.join("beta.meta"), b"{}").unwrap();
let config = KeychainConfig::with_keys_dir("test-app", dir.clone());
assert_eq!(list_labels(&config).unwrap(), vec!["alpha", "beta"]);
std::fs::remove_dir_all(dir).unwrap();
}
#[test]
fn load_pub_key_returns_cached_pub_without_touching_handle() {
let dir = std::env::temp_dir().join(format!(
"enclaveapp-apple-load-pub-cached-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
drop(std::fs::remove_dir_all(&dir));
std::fs::create_dir_all(&dir).unwrap();
let config = KeychainConfig::with_keys_dir("test-app", dir.clone());
let cached_pub = [0x04_u8; 65];
metadata::save_pub_key(&dir, "cached", &cached_pub).unwrap();
metadata::atomic_write(&dir.join("cached.handle"), &[0_u8; 32]).unwrap();
let got = load_pub_key(&config, "cached", KeyType::Signing).unwrap();
assert_eq!(got, cached_pub);
std::fs::remove_dir_all(dir).unwrap();
}
#[test]
fn load_pub_key_falls_through_when_cached_pub_is_malformed() {
let dir = std::env::temp_dir().join(format!(
"enclaveapp-apple-load-pub-malformed-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
drop(std::fs::remove_dir_all(&dir));
std::fs::create_dir_all(&dir).unwrap();
let config = KeychainConfig::with_keys_dir("test-app", dir.clone());
metadata::save_pub_key(&dir, "cached", &[0x05; 65]).unwrap();
metadata::atomic_write(&dir.join("cached.handle"), &[0_u8; 32]).unwrap();
let err = load_pub_key(&config, "cached", KeyType::Signing).unwrap_err();
assert!(matches!(err, Error::KeyOperation { .. }));
std::fs::remove_dir_all(dir).unwrap();
}
#[test]
fn load_pub_key_falls_through_when_cached_pub_is_missing() {
let dir = std::env::temp_dir().join(format!(
"enclaveapp-apple-load-pub-missing-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
drop(std::fs::remove_dir_all(&dir));
std::fs::create_dir_all(&dir).unwrap();
let config = KeychainConfig::with_keys_dir("test-app", dir.clone());
metadata::atomic_write(&dir.join("cached.handle"), &[0_u8; 32]).unwrap();
let err = load_pub_key(&config, "cached", KeyType::Signing).unwrap_err();
assert!(matches!(err, Error::KeyOperation { .. }));
std::fs::remove_dir_all(dir).unwrap();
}
#[test]
fn save_key_preserves_unreadable_handle_and_reports_error() {
let dir = std::env::temp_dir().join(format!(
"enclaveapp-apple-save-unreadable-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
drop(std::fs::remove_dir_all(&dir));
std::fs::create_dir_all(&dir).unwrap();
let handle_path = dir.join("existing.handle");
std::fs::write(&handle_path, b"handle").unwrap();
metadata::save_pub_key(&dir, "existing", &[0x04; 65]).unwrap();
metadata::save_meta(
&dir,
"existing",
&KeyMeta::new(
"existing",
KeyType::Signing,
crate::internal::core::AccessPolicy::None,
),
)
.unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&handle_path, std::fs::Permissions::from_mode(0o000)).unwrap();
}
let config = KeychainConfig::with_keys_dir("test-app", dir.clone());
let err = save_key(
&config,
"existing",
KeyType::Signing,
crate::internal::core::AccessPolicy::None,
b"new-handle",
&[0x04; 65],
)
.unwrap_err();
assert!(matches!(err, Error::KeyOperation { .. }));
assert!(dir.join("existing.handle").exists());
assert!(dir.join("existing.pub").exists());
assert!(dir.join("existing.meta").exists());
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&handle_path, std::fs::Permissions::from_mode(0o600)).ok();
}
std::fs::remove_dir_all(dir).unwrap();
}
}