Move macOS device key provider to Objective-C

This commit is contained in:
Ruslan Nigmatullin
2026-04-23 11:47:04 -07:00
parent 9b592ee45d
commit 6fd3bf8bbe
8 changed files with 584 additions and 401 deletions

View File

@@ -61,6 +61,7 @@ osx.frameworks(names = [
"IOSurface",
"IOKit",
"Kernel",
"LocalAuthentication",
"Metal",
"MetalKit",
"OpenGL",

5
codex-rs/Cargo.lock generated
View File

@@ -2538,13 +2538,10 @@ version = "0.0.0"
dependencies = [
"async-trait",
"base64 0.22.1",
"core-foundation 0.10.1",
"core-foundation-sys",
"cc",
"p256",
"pretty_assertions",
"rand 0.9.3",
"security-framework 3.5.1",
"security-framework-sys",
"serde",
"serde_json",
"thiserror 2.0.18",

View File

@@ -1,6 +1,27 @@
load("@rules_cc//cc:objc_library.bzl", "objc_library")
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "device-key",
crate_name = "codex_device_key",
# Bazel wires the Objective-C provider through :macos-provider below, so skip Cargo's build.rs.
build_script_enabled = False,
deps_extra = select({
"@platforms//os:macos": [":macos-provider"],
"//conditions:default": [],
}),
)
objc_library(
name = "macos-provider",
srcs = ["src/platform/macos_provider.m"],
hdrs = ["src/platform/macos_provider.h"],
copts = ["-fobjc-arc"],
sdk_frameworks = [
"Foundation",
"LocalAuthentication",
"Security",
],
target_compatible_with = ["@platforms//os:macos"],
visibility = ["//visibility:private"],
)

View File

@@ -3,6 +3,7 @@ name = "codex-device-key"
version.workspace = true
edition.workspace = true
license.workspace = true
build = "build.rs"
[lints]
workspace = true
@@ -18,11 +19,8 @@ thiserror = { workspace = true }
tokio = { workspace = true, features = ["rt"] }
url = { workspace = true }
[target.'cfg(target_os = "macos")'.dependencies]
core-foundation = { workspace = true }
core-foundation-sys = { workspace = true }
security-framework = { workspace = true }
security-framework-sys = { workspace = true }
[dev-dependencies]
pretty_assertions = { workspace = true }
[build-dependencies]
cc = "1"

View File

@@ -0,0 +1,17 @@
fn main() {
println!("cargo:rerun-if-changed=src/platform/macos_provider.h");
println!("cargo:rerun-if-changed=src/platform/macos_provider.m");
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("macos") {
return;
}
cc::Build::new()
.file("src/platform/macos_provider.m")
.flag("-fobjc-arc")
.compile("codex_device_key_macos_provider");
println!("cargo:rustc-link-lib=framework=Foundation");
println!("cargo:rustc-link-lib=framework=Security");
println!("cargo:rustc-link-lib=framework=LocalAuthentication");
}

View File

@@ -6,82 +6,84 @@ use crate::DeviceKeyProvider;
use crate::ProviderCreateRequest;
use crate::ProviderSignature;
use crate::sec1_public_key_to_spki_der;
use core_foundation::base::TCFType;
use core_foundation::base::ToVoid;
use core_foundation::boolean::CFBoolean;
use core_foundation::data::CFData;
use core_foundation::dictionary::CFMutableDictionary;
use core_foundation::error::CFError;
use core_foundation::number::CFNumber;
use core_foundation::string::CFString;
use core_foundation_sys::base::CFTypeRef;
use core_foundation_sys::string::CFStringRef;
use security_framework::access_control::ProtectionMode;
use security_framework::access_control::SecAccessControl;
use security_framework::key::Algorithm;
use security_framework::key::SecKey;
use security_framework_sys::access_control::kSecAccessControlPrivateKeyUsage;
use security_framework_sys::access_control::kSecAccessControlUserPresence;
use security_framework_sys::base::errSecItemNotFound;
use security_framework_sys::base::errSecParam;
use security_framework_sys::base::errSecSuccess;
use security_framework_sys::base::errSecUnimplemented;
use security_framework_sys::item::kSecAttrAccessControl;
use security_framework_sys::item::kSecAttrIsPermanent;
use security_framework_sys::item::kSecAttrKeyClass;
use security_framework_sys::item::kSecAttrKeyClassPrivate;
use security_framework_sys::item::kSecAttrKeySizeInBits;
use security_framework_sys::item::kSecAttrKeyType;
use security_framework_sys::item::kSecAttrKeyTypeECSECPrimeRandom;
use security_framework_sys::item::kSecAttrLabel;
use security_framework_sys::item::kSecAttrTokenID;
use security_framework_sys::item::kSecAttrTokenIDSecureEnclave;
use security_framework_sys::item::kSecClass;
use security_framework_sys::item::kSecClassKey;
use security_framework_sys::item::kSecPrivateKeyAttrs;
use security_framework_sys::item::kSecReturnRef;
use security_framework_sys::keychain_item::SecItemCopyMatching;
use std::ffi::CStr;
use std::ffi::CString;
use std::ffi::c_char;
use std::ffi::c_double;
use std::ffi::c_void;
use std::ffi::c_int;
use std::ptr;
use std::sync::Mutex;
use std::sync::OnceLock;
use std::slice;
#[allow(non_upper_case_globals)]
unsafe extern "C" {
static kSecAttrApplicationTag: CFStringRef;
static kSecAttrIsExtractable: CFStringRef;
static kSecUseAuthenticationContext: CFStringRef;
const MAC_STATUS_OK: c_int = 0;
const MAC_STATUS_NOT_FOUND: c_int = 1;
const MAC_STATUS_HARDWARE_UNAVAILABLE: c_int = 2;
const MAC_KEY_CLASS_SECURE_ENCLAVE: c_int = 0;
const MAC_KEY_CLASS_OS_PROTECTED_NONEXTRACTABLE: c_int = 1;
#[repr(C)]
struct MacBytesResult {
status: c_int,
data: *mut u8,
len: usize,
error_message: *mut c_char,
}
unsafe extern "C" {
fn dlopen(path: *const c_char, mode: i32) -> *mut c_void;
fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *mut c_void;
fn codex_device_key_macos_create_or_load_public_key(
key_tag: *const c_char,
key_class: c_int,
) -> MacBytesResult;
fn codex_device_key_macos_load_public_key(
key_tag: *const c_char,
key_class: c_int,
) -> MacBytesResult;
fn codex_device_key_macos_delete(key_tag: *const c_char, key_class: c_int) -> MacBytesResult;
fn codex_device_key_macos_sign(
key_tag: *const c_char,
key_class: c_int,
payload: *const u8,
payload_len: usize,
) -> MacBytesResult;
fn codex_device_key_macos_free_bytes_result(result: *mut MacBytesResult);
}
#[link(name = "objc")]
unsafe extern "C" {
fn objc_getClass(name: *const c_char) -> ObjcId;
fn sel_registerName(name: *const c_char) -> ObjcSel;
impl MacBytesResult {
fn into_bytes(mut self) -> Result<Vec<u8>, DeviceKeyError> {
let result = match self.status {
MAC_STATUS_OK => {
if self.data.is_null() && self.len != 0 {
Err(DeviceKeyError::Platform(
"macOS device-key provider returned null data".to_string(),
))
} else {
let bytes = if self.len == 0 {
Vec::new()
} else {
unsafe { slice::from_raw_parts(self.data.cast_const(), self.len).to_vec() }
};
Ok(bytes)
}
}
MAC_STATUS_NOT_FOUND => Err(DeviceKeyError::KeyNotFound),
MAC_STATUS_HARDWARE_UNAVAILABLE => Err(DeviceKeyError::HardwareBackedKeysUnavailable),
_ => Err(DeviceKeyError::Platform(self.error_message())),
};
unsafe {
codex_device_key_macos_free_bytes_result(ptr::addr_of_mut!(self));
}
result
}
fn error_message(&self) -> String {
if self.error_message.is_null() {
return "unknown macOS device-key provider error".to_string();
}
unsafe { CStr::from_ptr(self.error_message) }
.to_string_lossy()
.into_owned()
}
}
type ObjcId = *mut c_void;
type ObjcSel = *mut c_void;
const LOCAL_AUTHENTICATION_FRAMEWORK_PATH: &[u8] =
b"/System/Library/Frameworks/LocalAuthentication.framework/LocalAuthentication\0";
const LA_CONTEXT_CLASS: &[u8] = b"LAContext\0";
const OBJC_MSG_SEND_SYMBOL: &[u8] = b"objc_msgSend\0";
const OBJC_ALLOC_SELECTOR: &[u8] = b"alloc\0";
const OBJC_INIT_SELECTOR: &[u8] = b"init\0";
const OBJC_RELEASE_SELECTOR: &[u8] = b"release\0";
const SET_TOUCH_ID_AUTHENTICATION_REUSE_DURATION_SELECTOR: &[u8] =
b"setTouchIDAuthenticationAllowableReuseDuration:\0";
const TOUCH_ID_AUTHENTICATION_REUSE_DURATION_SECONDS: c_double = 300.0;
const RTLD_LAZY: i32 = 0x1;
const RTLD_LOCAL: i32 = 0x4;
#[derive(Debug)]
pub(crate) struct MacOsDeviceKeyProvider;
@@ -118,6 +120,16 @@ impl DeviceKeyProvider for MacOsDeviceKeyProvider {
}
}
fn delete(
&self,
key_id: &str,
protection_class: DeviceKeyProtectionClass,
) -> Result<(), DeviceKeyError> {
let class = MacKeyClass::from_protection_class(protection_class)
.ok_or(DeviceKeyError::KeyNotFound)?;
delete_key(key_id, class)
}
fn get_public(
&self,
key_id: &str,
@@ -125,8 +137,8 @@ impl DeviceKeyProvider for MacOsDeviceKeyProvider {
) -> Result<DeviceKeyInfo, DeviceKeyError> {
let class = MacKeyClass::from_protection_class(protection_class)
.ok_or(DeviceKeyError::KeyNotFound)?;
let key = load_private_key(key_id, class)?.ok_or(DeviceKeyError::KeyNotFound)?;
key_info(key_id, class, &key)
let public_key = load_public_key(key_id, class)?;
key_info(key_id, class, public_key.as_slice())
}
fn sign(
@@ -137,15 +149,7 @@ impl DeviceKeyProvider for MacOsDeviceKeyProvider {
) -> Result<ProviderSignature, DeviceKeyError> {
let class = MacKeyClass::from_protection_class(protection_class)
.ok_or(DeviceKeyError::KeyNotFound)?;
let context = reusable_authentication_context()?;
let context = context
.lock()
.map_err(|err| DeviceKeyError::Platform(format!("LAContext mutex poisoned: {err}")))?;
let key = load_private_key_with_authentication_context(key_id, class, &context)?
.ok_or(DeviceKeyError::KeyNotFound)?;
let signature_der = key
.create_signature(Algorithm::ECDSASignatureMessageX962SHA256, payload)
.map_err(|err| DeviceKeyError::Platform(err.to_string()))?;
let signature_der = sign(key_id, class, payload)?;
Ok(ProviderSignature {
signature_der,
algorithm: DeviceKeyAlgorithm::EcdsaP256Sha256,
@@ -153,146 +157,6 @@ impl DeviceKeyProvider for MacOsDeviceKeyProvider {
}
}
struct LocalAuthenticationContext {
context: ObjcId,
}
unsafe impl Send for LocalAuthenticationContext {}
impl LocalAuthenticationContext {
fn new() -> Result<Self, DeviceKeyError> {
load_local_authentication_framework()?;
let class = unsafe { objc_getClass(LA_CONTEXT_CLASS.as_ptr().cast::<c_char>()) };
if class.is_null() {
return Err(DeviceKeyError::Platform(
"LocalAuthentication.framework did not provide LAContext".to_string(),
));
}
let allocated = unsafe { objc_msg_send_id(class, sel(OBJC_ALLOC_SELECTOR))? };
if allocated.is_null() {
return Err(DeviceKeyError::Platform(
"LAContext allocation returned null".to_string(),
));
}
let context = unsafe { objc_msg_send_id(allocated, sel(OBJC_INIT_SELECTOR))? };
if context.is_null() {
return Err(DeviceKeyError::Platform(
"LAContext initialization returned null".to_string(),
));
}
unsafe {
objc_msg_send_void_f64(
context,
sel(SET_TOUCH_ID_AUTHENTICATION_REUSE_DURATION_SELECTOR),
TOUCH_ID_AUTHENTICATION_REUSE_DURATION_SECONDS,
)?;
}
Ok(Self { context })
}
fn as_void(&self) -> *const c_void {
self.context.cast_const()
}
}
impl Drop for LocalAuthenticationContext {
fn drop(&mut self) {
unsafe {
let _ = objc_msg_send_void(self.context, sel(OBJC_RELEASE_SELECTOR));
}
}
}
fn reusable_authentication_context()
-> Result<&'static Mutex<LocalAuthenticationContext>, DeviceKeyError> {
static AUTHENTICATION_CONTEXT: OnceLock<Mutex<LocalAuthenticationContext>> = OnceLock::new();
if let Some(context) = AUTHENTICATION_CONTEXT.get() {
return Ok(context);
}
let context = LocalAuthenticationContext::new()?;
if AUTHENTICATION_CONTEXT.set(Mutex::new(context)).is_err() {
return AUTHENTICATION_CONTEXT.get().ok_or_else(|| {
DeviceKeyError::Platform(
"LAContext initialization raced but no context won".to_string(),
)
});
}
AUTHENTICATION_CONTEXT.get().ok_or_else(|| {
DeviceKeyError::Platform("LAContext was not stored after initialization".to_string())
})
}
fn sel(name: &'static [u8]) -> ObjcSel {
unsafe { sel_registerName(name.as_ptr().cast::<c_char>()) }
}
fn load_local_authentication_framework() -> Result<(), DeviceKeyError> {
let handle = unsafe {
dlopen(
LOCAL_AUTHENTICATION_FRAMEWORK_PATH
.as_ptr()
.cast::<c_char>(),
RTLD_LAZY | RTLD_LOCAL,
)
};
if handle.is_null() {
Err(DeviceKeyError::Platform(
"failed to load LocalAuthentication.framework".to_string(),
))
} else {
Ok(())
}
}
unsafe fn objc_msg_send_id(receiver: ObjcId, selector: ObjcSel) -> Result<ObjcId, DeviceKeyError> {
let msg_send: unsafe extern "C" fn(ObjcId, ObjcSel) -> ObjcId =
unsafe { std::mem::transmute(objc_msg_send_symbol()?) };
Ok(unsafe { msg_send(receiver, selector) })
}
unsafe fn objc_msg_send_void(receiver: ObjcId, selector: ObjcSel) -> Result<(), DeviceKeyError> {
let msg_send: unsafe extern "C" fn(ObjcId, ObjcSel) =
unsafe { std::mem::transmute(objc_msg_send_symbol()?) };
unsafe { msg_send(receiver, selector) };
Ok(())
}
unsafe fn objc_msg_send_void_f64(
receiver: ObjcId,
selector: ObjcSel,
value: c_double,
) -> Result<(), DeviceKeyError> {
let msg_send: unsafe extern "C" fn(ObjcId, ObjcSel, c_double) =
unsafe { std::mem::transmute(objc_msg_send_symbol()?) };
unsafe { msg_send(receiver, selector, value) };
Ok(())
}
fn objc_msg_send_symbol() -> Result<*mut c_void, DeviceKeyError> {
let symbol = unsafe {
dlsym(
rtld_default(),
OBJC_MSG_SEND_SYMBOL.as_ptr().cast::<c_char>(),
)
};
if symbol.is_null() {
Err(DeviceKeyError::Platform(
"objc_msgSend lookup returned null".to_string(),
))
} else {
Ok(symbol)
}
}
fn rtld_default() -> *mut c_void {
(-2_isize) as *mut c_void
}
#[derive(Debug, Clone, Copy)]
enum MacKeyClass {
SecureEnclave,
@@ -300,6 +164,13 @@ enum MacKeyClass {
}
impl MacKeyClass {
fn native(self) -> c_int {
match self {
Self::SecureEnclave => MAC_KEY_CLASS_SECURE_ENCLAVE,
Self::OsProtectedNonextractable => MAC_KEY_CLASS_OS_PROTECTED_NONEXTRACTABLE,
}
}
fn protection_class(self) -> DeviceKeyProtectionClass {
match self {
Self::SecureEnclave => DeviceKeyProtectionClass::HardwareSecureEnclave,
@@ -325,198 +196,62 @@ impl MacKeyClass {
}
}
fn load_private_key(key_id: &str, class: MacKeyClass) -> Result<Option<SecKey>, DeviceKeyError> {
load_private_key_with_optional_authentication_context(
key_id, class, /*authentication_context*/ None,
)
}
fn load_private_key_with_authentication_context(
key_id: &str,
class: MacKeyClass,
authentication_context: &LocalAuthenticationContext,
) -> Result<Option<SecKey>, DeviceKeyError> {
load_private_key_with_optional_authentication_context(
key_id,
class,
Some(authentication_context),
)
}
fn load_private_key_with_optional_authentication_context(
key_id: &str,
class: MacKeyClass,
authentication_context: Option<&LocalAuthenticationContext>,
) -> Result<Option<SecKey>, DeviceKeyError> {
let tag = key_tag(key_id, class);
let tag = CFData::from_buffer(tag.as_bytes());
let mut query = unsafe {
CFMutableDictionary::from_CFType_pairs(&[
(kSecClass.to_void(), kSecClassKey.to_void()),
(
kSecAttrKeyClass.to_void(),
kSecAttrKeyClassPrivate.to_void(),
),
(kSecAttrApplicationTag.to_void(), tag.to_void()),
(kSecReturnRef.to_void(), CFBoolean::true_value().to_void()),
])
};
if matches!(class, MacKeyClass::SecureEnclave) {
unsafe {
query.add(
&kSecAttrTokenID.to_void(),
&kSecAttrTokenIDSecureEnclave.to_void(),
);
}
}
if matches!(class, MacKeyClass::OsProtectedNonextractable) {
unsafe {
query.add(
&kSecAttrIsExtractable.to_void(),
&CFBoolean::false_value().to_void(),
);
}
}
if let Some(authentication_context) = authentication_context {
unsafe {
query.add(
&kSecUseAuthenticationContext.to_void(),
&authentication_context.as_void(),
);
}
}
let mut result: CFTypeRef = ptr::null();
let status = unsafe { SecItemCopyMatching(query.as_concrete_TypeRef(), &mut result) };
if status == errSecItemNotFound {
return Ok(None);
}
if status != errSecSuccess {
return Err(DeviceKeyError::Platform(security_error(status)));
}
if result.is_null() {
return Err(DeviceKeyError::Platform(
"Security.framework returned an empty key reference".to_string(),
));
}
Ok(Some(unsafe {
SecKey::wrap_under_create_rule(result as *mut _)
}))
}
fn create_or_load_key_info(
key_id: &str,
class: MacKeyClass,
) -> Result<DeviceKeyInfo, DeviceKeyError> {
let key = create_or_load_private_key(key_id, class)?;
key_info(key_id, class, &key)
let public_key = create_or_load_public_key(key_id, class)?;
key_info(key_id, class, public_key.as_slice())
}
fn create_or_load_private_key(key_id: &str, class: MacKeyClass) -> Result<SecKey, DeviceKeyError> {
match create_private_key(key_id, class) {
Ok(key) => Ok(key),
Err(create_error) => match load_private_key(key_id, class) {
Ok(Some(key)) => Ok(key),
Ok(None) => Err(create_error),
Err(load_error) => Err(DeviceKeyError::Platform(format!(
"key creation failed ({create_error}); reload failed ({load_error})"
))),
},
}
fn create_or_load_public_key(key_id: &str, class: MacKeyClass) -> Result<Vec<u8>, DeviceKeyError> {
let tag = key_tag_cstring(key_id, class)?;
unsafe { codex_device_key_macos_create_or_load_public_key(tag.as_ptr(), class.native()) }
.into_bytes()
}
/// Creates a macOS this-device-only P-256 signing key.
///
/// The access-control flags below keep the private key local to this device and require
/// Security.framework to prove user presence before private-key use. The signing path also passes a
/// process-local `LAContext` so successful biometric/password authentication can be reused for later
/// signatures when macOS policy allows it.
#[allow(deprecated)]
fn create_private_key(key_id: &str, class: MacKeyClass) -> Result<SecKey, DeviceKeyError> {
let access_control = SecAccessControl::create_with_protection(
Some(ProtectionMode::AccessibleWhenUnlockedThisDeviceOnly),
kSecAccessControlPrivateKeyUsage | kSecAccessControlUserPresence,
)
.map_err(|err| DeviceKeyError::Platform(err.to_string()))?;
let tag = key_tag(key_id, class);
let tag_data = CFData::from_buffer(tag.as_bytes());
let label = CFString::new(&tag);
let key_size = CFNumber::from(256);
let mut private_attrs = unsafe {
CFMutableDictionary::from_CFType_pairs(&[
(
kSecAttrIsPermanent.to_void(),
CFBoolean::true_value().to_void(),
),
(kSecAttrAccessControl.to_void(), access_control.to_void()),
(kSecAttrApplicationTag.to_void(), tag_data.to_void()),
(kSecAttrLabel.to_void(), label.to_void()),
])
};
if matches!(class, MacKeyClass::OsProtectedNonextractable) {
unsafe {
private_attrs.add(
&kSecAttrIsExtractable.to_void(),
&CFBoolean::false_value().to_void(),
);
}
}
let mut attributes = unsafe {
CFMutableDictionary::from_CFType_pairs(&[
(
kSecAttrKeyType.to_void(),
kSecAttrKeyTypeECSECPrimeRandom.to_void(),
),
(kSecAttrKeySizeInBits.to_void(), key_size.to_void()),
(kSecAttrLabel.to_void(), label.to_void()),
(kSecPrivateKeyAttrs.to_void(), private_attrs.to_void()),
])
};
if matches!(class, MacKeyClass::SecureEnclave) {
unsafe {
attributes.add(
&kSecAttrTokenID.to_void(),
&kSecAttrTokenIDSecureEnclave.to_void(),
);
}
}
SecKey::generate(attributes.to_immutable()).map_err(|err| create_key_error(class, err))
fn load_public_key(key_id: &str, class: MacKeyClass) -> Result<Vec<u8>, DeviceKeyError> {
let tag = key_tag_cstring(key_id, class)?;
unsafe { codex_device_key_macos_load_public_key(tag.as_ptr(), class.native()) }.into_bytes()
}
fn create_key_error(class: MacKeyClass, error: CFError) -> DeviceKeyError {
let code = error.code() as i32;
if matches!(class, MacKeyClass::SecureEnclave)
&& (code == errSecUnimplemented || code == errSecParam)
{
return DeviceKeyError::HardwareBackedKeysUnavailable;
}
fn delete_key(key_id: &str, class: MacKeyClass) -> Result<(), DeviceKeyError> {
let tag = key_tag_cstring(key_id, class)?;
unsafe { codex_device_key_macos_delete(tag.as_ptr(), class.native()) }
.into_bytes()
.map(|_| ())
}
DeviceKeyError::Platform(error.description().to_string())
fn sign(key_id: &str, class: MacKeyClass, payload: &[u8]) -> Result<Vec<u8>, DeviceKeyError> {
let tag = key_tag_cstring(key_id, class)?;
unsafe {
codex_device_key_macos_sign(
tag.as_ptr(),
class.native(),
payload.as_ptr(),
payload.len(),
)
}
.into_bytes()
}
fn key_info(
key_id: &str,
class: MacKeyClass,
private_key: &SecKey,
sec1_public_key: &[u8],
) -> Result<DeviceKeyInfo, DeviceKeyError> {
let public_key = private_key.public_key().ok_or_else(|| {
DeviceKeyError::Platform("Security.framework did not return a public key".to_string())
})?;
let public_key = public_key.external_representation().ok_or_else(|| {
DeviceKeyError::Platform(
"Security.framework did not return an exportable public key".to_string(),
)
})?;
Ok(DeviceKeyInfo {
key_id: key_id.to_string(),
public_key_spki_der: sec1_public_key_to_spki_der(&public_key)?,
public_key_spki_der: sec1_public_key_to_spki_der(sec1_public_key)?,
algorithm: DeviceKeyAlgorithm::EcdsaP256Sha256,
protection_class: class.protection_class(),
})
}
fn key_tag_cstring(key_id: &str, class: MacKeyClass) -> Result<CString, DeviceKeyError> {
CString::new(key_tag(key_id, class)).map_err(|err| DeviceKeyError::Platform(err.to_string()))
}
fn key_tag(key_id: &str, class: MacKeyClass) -> String {
format!(
"com.openai.codex.device-key.{}.{}",
@@ -524,9 +259,3 @@ fn key_tag(key_id: &str, class: MacKeyClass) -> String {
key_id
)
}
fn security_error(status: i32) -> String {
security_framework::base::Error::from_code(status)
.message()
.unwrap_or_else(|| format!("Security.framework error code {status}"))
}

View File

@@ -0,0 +1,50 @@
#ifndef CODEX_DEVICE_KEY_MACOS_PROVIDER_H
#define CODEX_DEVICE_KEY_MACOS_PROVIDER_H
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef enum CodexDeviceKeyMacStatus {
CodexDeviceKeyMacStatusOk = 0,
CodexDeviceKeyMacStatusNotFound = 1,
CodexDeviceKeyMacStatusHardwareUnavailable = 2,
CodexDeviceKeyMacStatusPlatformError = 3,
} CodexDeviceKeyMacStatus;
typedef enum CodexDeviceKeyMacKeyClass {
CodexDeviceKeyMacKeyClassSecureEnclave = 0,
CodexDeviceKeyMacKeyClassOsProtectedNonextractable = 1,
} CodexDeviceKeyMacKeyClass;
typedef struct CodexDeviceKeyMacBytesResult {
int32_t status;
uint8_t *data;
size_t len;
char *error_message;
} CodexDeviceKeyMacBytesResult;
CodexDeviceKeyMacBytesResult codex_device_key_macos_create_or_load_public_key(
const char *key_tag,
int32_t key_class);
CodexDeviceKeyMacBytesResult codex_device_key_macos_load_public_key(
const char *key_tag,
int32_t key_class);
CodexDeviceKeyMacBytesResult codex_device_key_macos_delete(
const char *key_tag,
int32_t key_class);
CodexDeviceKeyMacBytesResult codex_device_key_macos_sign(
const char *key_tag,
int32_t key_class,
const uint8_t *payload,
size_t payload_len);
void codex_device_key_macos_free_bytes_result(CodexDeviceKeyMacBytesResult *result);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,370 @@
#import "macos_provider.h"
#import <Foundation/Foundation.h>
#import <LocalAuthentication/LocalAuthentication.h>
#import <Security/Security.h>
#include <stdlib.h>
#include <string.h>
static NSTimeInterval const CodexDeviceKeyTouchIdReuseDurationSeconds = 300.0;
static CodexDeviceKeyMacBytesResult CodexDeviceKeyMacResultMake(
CodexDeviceKeyMacStatus status,
NSData *data,
NSString *errorMessage) {
CodexDeviceKeyMacBytesResult result = {
.status = status,
.data = NULL,
.len = 0,
.error_message = NULL,
};
if (data.length > 0) {
result.data = malloc(data.length);
if (result.data == NULL) {
result.status = CodexDeviceKeyMacStatusPlatformError;
errorMessage = @"failed to allocate result bytes";
} else {
memcpy(result.data, data.bytes, data.length);
result.len = data.length;
}
}
if (errorMessage.length > 0) {
const char *utf8 = errorMessage.UTF8String;
if (utf8 != NULL) {
size_t len = strlen(utf8);
result.error_message = malloc(len + 1);
if (result.error_message != NULL) {
memcpy(result.error_message, utf8, len + 1);
}
}
}
return result;
}
static CodexDeviceKeyMacBytesResult CodexDeviceKeyMacError(
CodexDeviceKeyMacStatus status,
NSString *message) {
return CodexDeviceKeyMacResultMake(status, nil, message);
}
static NSString *CodexDeviceKeyMacCopySecurityError(OSStatus status) {
NSString *message = CFBridgingRelease(SecCopyErrorMessageString(status, NULL));
if (message.length > 0) {
return message;
}
return [NSString stringWithFormat:@"Security.framework error code %d", status];
}
static NSString *CodexDeviceKeyMacCopyCFError(CFErrorRef error) {
if (error == NULL) {
return @"Security.framework returned an unknown error";
}
NSError *nsError = CFBridgingRelease(error);
if (nsError.localizedDescription.length > 0) {
return nsError.localizedDescription;
}
return [nsError description];
}
static BOOL CodexDeviceKeyMacClassIsValid(int32_t keyClass) {
return keyClass == CodexDeviceKeyMacKeyClassSecureEnclave ||
keyClass == CodexDeviceKeyMacKeyClassOsProtectedNonextractable;
}
static NSData *CodexDeviceKeyMacTagData(NSString *keyTag) {
return [keyTag dataUsingEncoding:NSUTF8StringEncoding];
}
static NSMutableDictionary *CodexDeviceKeyMacPrivateKeyQuery(
NSString *keyTag,
int32_t keyClass,
LAContext *authenticationContext) {
NSMutableDictionary *query = [@{
(__bridge id)kSecClass: (__bridge id)kSecClassKey,
(__bridge id)kSecAttrKeyClass: (__bridge id)kSecAttrKeyClassPrivate,
(__bridge id)kSecAttrApplicationTag: CodexDeviceKeyMacTagData(keyTag),
(__bridge id)kSecReturnRef: @YES,
} mutableCopy];
if (keyClass == CodexDeviceKeyMacKeyClassSecureEnclave) {
query[(__bridge id)kSecAttrTokenID] = (__bridge id)kSecAttrTokenIDSecureEnclave;
} else {
query[(__bridge id)kSecAttrIsExtractable] = @NO;
}
if (authenticationContext != nil) {
query[(__bridge id)kSecUseAuthenticationContext] = authenticationContext;
}
return query;
}
static SecKeyRef CodexDeviceKeyMacCopyPrivateKey(
NSString *keyTag,
int32_t keyClass,
LAContext *authenticationContext,
CodexDeviceKeyMacStatus *status,
NSString **errorMessage) {
CFTypeRef item = NULL;
OSStatus secStatus = SecItemCopyMatching(
(__bridge CFDictionaryRef)CodexDeviceKeyMacPrivateKeyQuery(
keyTag, keyClass, authenticationContext),
&item);
if (secStatus == errSecItemNotFound) {
*status = CodexDeviceKeyMacStatusNotFound;
return NULL;
}
if (secStatus != errSecSuccess) {
*status = CodexDeviceKeyMacStatusPlatformError;
*errorMessage = CodexDeviceKeyMacCopySecurityError(secStatus);
return NULL;
}
if (item == NULL) {
*status = CodexDeviceKeyMacStatusPlatformError;
*errorMessage = @"Security.framework returned an empty key reference";
return NULL;
}
*status = CodexDeviceKeyMacStatusOk;
return (SecKeyRef)item;
}
static SecKeyRef CodexDeviceKeyMacCreatePrivateKey(
NSString *keyTag,
int32_t keyClass,
CodexDeviceKeyMacStatus *status,
NSString **errorMessage) {
CFErrorRef accessControlError = NULL;
SecAccessControlRef accessControl = SecAccessControlCreateWithFlags(
kCFAllocatorDefault,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
kSecAccessControlPrivateKeyUsage | kSecAccessControlUserPresence,
&accessControlError);
if (accessControl == NULL) {
*status = CodexDeviceKeyMacStatusPlatformError;
*errorMessage = CodexDeviceKeyMacCopyCFError(accessControlError);
return NULL;
}
NSMutableDictionary *privateAttributes = [@{
(__bridge id)kSecAttrIsPermanent: @YES,
(__bridge id)kSecAttrAccessControl: (__bridge id)accessControl,
(__bridge id)kSecAttrApplicationTag: CodexDeviceKeyMacTagData(keyTag),
(__bridge id)kSecAttrLabel: keyTag,
} mutableCopy];
if (keyClass == CodexDeviceKeyMacKeyClassOsProtectedNonextractable) {
privateAttributes[(__bridge id)kSecAttrIsExtractable] = @NO;
}
NSMutableDictionary *attributes = [@{
(__bridge id)kSecAttrKeyType: (__bridge id)kSecAttrKeyTypeECSECPrimeRandom,
(__bridge id)kSecAttrKeySizeInBits: @256,
(__bridge id)kSecAttrLabel: keyTag,
(__bridge id)kSecPrivateKeyAttrs: privateAttributes,
} mutableCopy];
if (keyClass == CodexDeviceKeyMacKeyClassSecureEnclave) {
attributes[(__bridge id)kSecAttrTokenID] = (__bridge id)kSecAttrTokenIDSecureEnclave;
}
CFErrorRef createError = NULL;
SecKeyRef key = SecKeyCreateRandomKey((__bridge CFDictionaryRef)attributes, &createError);
CFRelease(accessControl);
if (key != NULL) {
*status = CodexDeviceKeyMacStatusOk;
return key;
}
NSError *nsError = createError == NULL ? nil : CFBridgingRelease(createError);
NSInteger code = nsError == nil ? 0 : nsError.code;
if (keyClass == CodexDeviceKeyMacKeyClassSecureEnclave &&
(code == errSecUnimplemented || code == errSecParam)) {
*status = CodexDeviceKeyMacStatusHardwareUnavailable;
return NULL;
}
*status = CodexDeviceKeyMacStatusPlatformError;
*errorMessage = nsError.localizedDescription.length > 0
? nsError.localizedDescription
: @"Security.framework failed to create a private key";
return NULL;
}
static CodexDeviceKeyMacBytesResult CodexDeviceKeyMacCopyPublicKeyResult(SecKeyRef privateKey) {
SecKeyRef publicKey = SecKeyCopyPublicKey(privateKey);
if (publicKey == NULL) {
return CodexDeviceKeyMacError(
CodexDeviceKeyMacStatusPlatformError,
@"Security.framework did not return a public key");
}
CFErrorRef error = NULL;
CFDataRef publicKeyData = SecKeyCopyExternalRepresentation(publicKey, &error);
CFRelease(publicKey);
if (publicKeyData == NULL) {
return CodexDeviceKeyMacError(
CodexDeviceKeyMacStatusPlatformError,
CodexDeviceKeyMacCopyCFError(error));
}
NSData *data = CFBridgingRelease(publicKeyData);
return CodexDeviceKeyMacResultMake(CodexDeviceKeyMacStatusOk, data, nil);
}
static LAContext *CodexDeviceKeyMacReusableAuthenticationContext(void) {
static LAContext *context = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
context = [[LAContext alloc] init];
context.touchIDAuthenticationAllowableReuseDuration =
CodexDeviceKeyTouchIdReuseDurationSeconds;
});
return context;
}
CodexDeviceKeyMacBytesResult codex_device_key_macos_create_or_load_public_key(
const char *keyTag,
int32_t keyClass) {
@autoreleasepool {
if (keyTag == NULL || !CodexDeviceKeyMacClassIsValid(keyClass)) {
return CodexDeviceKeyMacError(
CodexDeviceKeyMacStatusPlatformError,
@"invalid macOS device-key provider argument");
}
NSString *tag = [NSString stringWithUTF8String:keyTag];
CodexDeviceKeyMacStatus status = CodexDeviceKeyMacStatusOk;
NSString *errorMessage = nil;
SecKeyRef key = CodexDeviceKeyMacCreatePrivateKey(tag, keyClass, &status, &errorMessage);
if (key == NULL) {
if (status == CodexDeviceKeyMacStatusHardwareUnavailable) {
return CodexDeviceKeyMacError(status, nil);
}
CodexDeviceKeyMacStatus loadStatus = CodexDeviceKeyMacStatusOk;
NSString *loadErrorMessage = nil;
key = CodexDeviceKeyMacCopyPrivateKey(
tag, keyClass, nil, &loadStatus, &loadErrorMessage);
if (key == NULL) {
if (loadStatus == CodexDeviceKeyMacStatusNotFound) {
return CodexDeviceKeyMacError(status, errorMessage);
}
return CodexDeviceKeyMacError(
CodexDeviceKeyMacStatusPlatformError,
[NSString stringWithFormat:
@"key creation failed (%@); reload failed (%@)",
errorMessage ?: @"unknown error",
loadErrorMessage ?: @"unknown error"]);
}
}
CodexDeviceKeyMacBytesResult result = CodexDeviceKeyMacCopyPublicKeyResult(key);
CFRelease(key);
return result;
}
}
CodexDeviceKeyMacBytesResult codex_device_key_macos_load_public_key(
const char *keyTag,
int32_t keyClass) {
@autoreleasepool {
if (keyTag == NULL || !CodexDeviceKeyMacClassIsValid(keyClass)) {
return CodexDeviceKeyMacError(
CodexDeviceKeyMacStatusPlatformError,
@"invalid macOS device-key provider argument");
}
NSString *tag = [NSString stringWithUTF8String:keyTag];
CodexDeviceKeyMacStatus status = CodexDeviceKeyMacStatusOk;
NSString *errorMessage = nil;
SecKeyRef key = CodexDeviceKeyMacCopyPrivateKey(tag, keyClass, nil, &status, &errorMessage);
if (key == NULL) {
return CodexDeviceKeyMacError(status, errorMessage);
}
CodexDeviceKeyMacBytesResult result = CodexDeviceKeyMacCopyPublicKeyResult(key);
CFRelease(key);
return result;
}
}
CodexDeviceKeyMacBytesResult codex_device_key_macos_delete(
const char *keyTag,
int32_t keyClass) {
@autoreleasepool {
if (keyTag == NULL || !CodexDeviceKeyMacClassIsValid(keyClass)) {
return CodexDeviceKeyMacError(
CodexDeviceKeyMacStatusPlatformError,
@"invalid macOS device-key provider argument");
}
NSString *tag = [NSString stringWithUTF8String:keyTag];
NSMutableDictionary *query = CodexDeviceKeyMacPrivateKeyQuery(tag, keyClass, nil);
[query removeObjectForKey:(__bridge id)kSecReturnRef];
OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
if (status == errSecSuccess || status == errSecItemNotFound) {
return CodexDeviceKeyMacResultMake(CodexDeviceKeyMacStatusOk, nil, nil);
}
return CodexDeviceKeyMacError(
CodexDeviceKeyMacStatusPlatformError,
CodexDeviceKeyMacCopySecurityError(status));
}
}
CodexDeviceKeyMacBytesResult codex_device_key_macos_sign(
const char *keyTag,
int32_t keyClass,
const uint8_t *payload,
size_t payloadLen) {
@autoreleasepool {
if (keyTag == NULL || payload == NULL || !CodexDeviceKeyMacClassIsValid(keyClass)) {
return CodexDeviceKeyMacError(
CodexDeviceKeyMacStatusPlatformError,
@"invalid macOS device-key provider argument");
}
NSString *tag = [NSString stringWithUTF8String:keyTag];
CodexDeviceKeyMacStatus status = CodexDeviceKeyMacStatusOk;
NSString *errorMessage = nil;
SecKeyRef key = CodexDeviceKeyMacCopyPrivateKey(
tag,
keyClass,
CodexDeviceKeyMacReusableAuthenticationContext(),
&status,
&errorMessage);
if (key == NULL) {
return CodexDeviceKeyMacError(status, errorMessage);
}
NSData *payloadData = [NSData dataWithBytes:payload length:payloadLen];
CFErrorRef error = NULL;
CFDataRef signature = SecKeyCreateSignature(
key,
kSecKeyAlgorithmECDSASignatureMessageX962SHA256,
(__bridge CFDataRef)payloadData,
&error);
CFRelease(key);
if (signature == NULL) {
return CodexDeviceKeyMacError(
CodexDeviceKeyMacStatusPlatformError,
CodexDeviceKeyMacCopyCFError(error));
}
NSData *signatureData = CFBridgingRelease(signature);
return CodexDeviceKeyMacResultMake(CodexDeviceKeyMacStatusOk, signatureData, nil);
}
}
void codex_device_key_macos_free_bytes_result(CodexDeviceKeyMacBytesResult *result) {
if (result == NULL) {
return;
}
free(result->data);
free(result->error_message);
result->data = NULL;
result->len = 0;
result->error_message = NULL;
}