mirror of
https://github.com/openai/codex.git
synced 2026-11-06 20:20:34 +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
87 lines
2.9 KiB
Rust
87 lines
2.9 KiB
Rust
//! Serializes operations for one account across local processes, with cancellable lock waits.
|
|
|
|
use crate::UserVerificationError;
|
|
use crate::UserVerificationFailureReason;
|
|
use crate::UserVerificationRequestGuard;
|
|
use std::fs;
|
|
use std::fs::File;
|
|
use std::fs::OpenOptions;
|
|
use std::path::Path;
|
|
use std::time::Duration;
|
|
use std::time::Instant;
|
|
|
|
pub(crate) struct LifecycleLock {
|
|
_file: File,
|
|
}
|
|
|
|
impl LifecycleLock {
|
|
#[cfg(target_os = "macos")]
|
|
pub(crate) fn acquire(
|
|
namespace: &crate::UserVerificationKeyNamespace,
|
|
guard: &UserVerificationRequestGuard,
|
|
) -> Result<Self, UserVerificationError> {
|
|
let directory = dirs::data_local_dir().ok_or_else(|| UserVerificationError::Failed {
|
|
reason: UserVerificationFailureReason::ProviderError,
|
|
message: "could not locate the credential lock directory".to_string(),
|
|
})?;
|
|
Self::acquire_at(
|
|
&directory
|
|
.join("com.openai.codex")
|
|
.join("user-verification")
|
|
.join(format!("{}.lock", namespace.label)),
|
|
Duration::from_secs(/*secs*/ 60),
|
|
guard,
|
|
)
|
|
}
|
|
|
|
fn acquire_at(
|
|
path: &Path,
|
|
timeout: Duration,
|
|
guard: &UserVerificationRequestGuard,
|
|
) -> Result<Self, UserVerificationError> {
|
|
guard.check()?;
|
|
if let Some(parent) = path.parent() {
|
|
fs::create_dir_all(parent).map_err(lock_error)?;
|
|
}
|
|
let file = OpenOptions::new()
|
|
.read(true)
|
|
.write(true)
|
|
.create(true)
|
|
.truncate(false)
|
|
.open(path)
|
|
.map_err(lock_error)?;
|
|
let started = Instant::now();
|
|
loop {
|
|
guard.check()?;
|
|
match file.try_lock() {
|
|
Ok(()) => {
|
|
guard.check()?;
|
|
return Ok(Self { _file: file });
|
|
}
|
|
Err(std::fs::TryLockError::WouldBlock) if started.elapsed() >= timeout => {
|
|
return Err(UserVerificationError::Failed {
|
|
reason: UserVerificationFailureReason::Timeout,
|
|
message: "timed out waiting for another credential operation".to_string(),
|
|
});
|
|
}
|
|
Err(std::fs::TryLockError::WouldBlock) => {
|
|
std::thread::sleep(Duration::from_millis(/*millis*/ 50).min(timeout));
|
|
}
|
|
Err(std::fs::TryLockError::Error(error)) => return Err(lock_error(error)),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn lock_error(error: std::io::Error) -> UserVerificationError {
|
|
tracing::warn!(%error, "user-verification lifecycle lock failed");
|
|
UserVerificationError::Failed {
|
|
reason: UserVerificationFailureReason::ProviderError,
|
|
message: "could not acquire the credential lock".to_string(),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[path = "lifecycle_lock_tests.rs"]
|
|
mod tests;
|