Files
codex/codex-rs/user-verification/src/lifecycle_lock_tests.rs
T
riley-oai e7637306bc Add macOS user verification with Secure Enclave signing (#43624)
## Why

The user-verification API previously returned an unsupported provider on macOS. Enable native verification using biometric-protected credentials.

## What changed

- Implement credential status, creation, reuse, deletion, and challenge signing with P-256 Secure Enclave keys in the Data Protection Keychain.
- Require biometric authentication through the key's access-control policy and use a fresh `LAContext` for each signature.
- Report Touch ID hardware support independently of enrollment and current readiness, and map native failures to structured verification errors.
- Serialize credential operations across processes with cancellable file-lock waits. Invalidate pending authentication on cancellation and wait for the signer to exit before releasing the lock.

## Testing

Add unit tests for lock contention and cancellation, authentication teardown and late-result rejection, native error classification, and validation of Secure Enclave key attributes.

GitOrigin-RevId: 93268962cd824b2befc1a0c54b99dcb77a0f1044
2026-09-08 00:13:28 +00:00

61 lines
2.3 KiB
Rust

//! Exercises real file-lock contention and cancellation without releasing the held lock.
use super::*;
use crate::UserVerificationCancellationReason;
use pretty_assertions::assert_eq;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
#[test]
fn lock_serializes_operations_and_releases_on_drop() {
let directory = tempfile::tempdir().expect("temporary directory");
let path = directory.path().join("credential.lock");
let guard = UserVerificationRequestGuard::default();
let first = LifecycleLock::acquire_at(&path, Duration::ZERO, &guard).expect("first lock");
assert_eq!(
LifecycleLock::acquire_at(&path, Duration::ZERO, &guard).err(),
Some(UserVerificationError::Failed {
reason: UserVerificationFailureReason::Timeout,
message: "timed out waiting for another credential operation".to_string(),
})
);
drop(first);
LifecycleLock::acquire_at(&path, Duration::ZERO, &guard).expect("lock after release");
}
#[test]
fn cancelled_waiter_stops_while_another_operation_still_holds_lock() {
let directory = tempfile::tempdir().expect("temporary directory");
let path = directory.path().join("credential.lock");
let holder = LifecycleLock::acquire_at(
&path,
Duration::ZERO,
&UserVerificationRequestGuard::default(),
)
.expect("held lock");
let (ready_tx, ready_rx) = std::sync::mpsc::channel();
let checks = AtomicUsize::new(/*v*/ 0);
let guard = UserVerificationRequestGuard::with_activity_check(move || {
if checks.fetch_add(/*val*/ 1, Ordering::Relaxed) == 1 {
ready_tx.send(()).expect("notify lock wait");
}
true
});
let queued = guard.clone();
let worker = std::thread::spawn(move || {
LifecycleLock::acquire_at(&path, Duration::from_secs(/*secs*/ 5), &queued).err()
});
ready_rx
.recv_timeout(Duration::from_secs(/*secs*/ 5))
.expect("waiter entered lock loop");
guard.cancel();
assert_eq!(
worker.join().expect("waiter stopped"),
Some(UserVerificationError::Cancelled {
reason: UserVerificationCancellationReason::Interrupted,
message: "the verification operation is no longer active".to_string(),
})
);
drop(holder);
}