mirror of
https://github.com/openai/codex.git
synced 2026-09-08 15:50:34 +00:00
Move macOS device key provider to Objective-C
This commit is contained in:
@@ -61,6 +61,7 @@ osx.frameworks(names = [
|
||||
"IOSurface",
|
||||
"IOKit",
|
||||
"Kernel",
|
||||
"LocalAuthentication",
|
||||
"Metal",
|
||||
"MetalKit",
|
||||
"OpenGL",
|
||||
|
||||
5
codex-rs/Cargo.lock
generated
5
codex-rs/Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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"],
|
||||
)
|
||||
|
||||
@@ -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"
|
||||
|
||||
17
codex-rs/device-key/build.rs
Normal file
17
codex-rs/device-key/build.rs
Normal 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");
|
||||
}
|
||||
@@ -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}"))
|
||||
}
|
||||
|
||||
50
codex-rs/device-key/src/platform/macos_provider.h
Normal file
50
codex-rs/device-key/src/platform/macos_provider.h
Normal 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
|
||||
370
codex-rs/device-key/src/platform/macos_provider.m
Normal file
370
codex-rs/device-key/src/platform/macos_provider.m
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user