mirror of
https://github.com/openai/codex.git
synced 2026-10-19 17:28:46 +00:00
## 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
61 lines
2.3 KiB
Rust
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);
|
|
}
|