mirror of
https://github.com/openai/codex.git
synced 2026-05-01 09:56:37 +00:00
Compare commits
10 Commits
owen/perfo
...
ruslan/dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eed4fccd03 | ||
|
|
21ab239bc5 | ||
|
|
e06c0a480d | ||
|
|
8d431c0c50 | ||
|
|
972149926d | ||
|
|
6fd3bf8bbe | ||
|
|
9b592ee45d | ||
|
|
4785b945e8 | ||
|
|
08b0d83b66 | ||
|
|
72a6cab442 |
@@ -61,6 +61,7 @@ osx.frameworks(names = [
|
||||
"IOSurface",
|
||||
"IOKit",
|
||||
"Kernel",
|
||||
"LocalAuthentication",
|
||||
"Metal",
|
||||
"MetalKit",
|
||||
"OpenGL",
|
||||
|
||||
1
codex-rs/Cargo.lock
generated
1
codex-rs/Cargo.lock
generated
@@ -2538,6 +2538,7 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
"cc",
|
||||
"p256",
|
||||
"pretty_assertions",
|
||||
"rand 0.9.3",
|
||||
|
||||
@@ -237,6 +237,7 @@ clap = "4"
|
||||
clap_complete = "4"
|
||||
color-eyre = "0.6.3"
|
||||
constant_time_eq = "0.3.1"
|
||||
core-foundation = "0.10.1"
|
||||
crossbeam-channel = "0.5.15"
|
||||
crypto_box = { version = "0.9.1", features = ["seal"] }
|
||||
crossterm = "0.28.1"
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
tags = ["manual"],
|
||||
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
|
||||
@@ -20,3 +21,6 @@ url = { 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");
|
||||
}
|
||||
@@ -1,49 +1,63 @@
|
||||
use crate::DeviceKeyError;
|
||||
use crate::DeviceKeyInfo;
|
||||
use crate::DeviceKeyProtectionClass;
|
||||
use crate::DeviceKeyProvider;
|
||||
use crate::ProviderCreateRequest;
|
||||
use crate::ProviderSignature;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod macos;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub(crate) fn default_provider() -> Arc<dyn DeviceKeyProvider> {
|
||||
Arc::new(UnsupportedDeviceKeyProvider)
|
||||
Arc::new(macos::MacOsDeviceKeyProvider)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct UnsupportedDeviceKeyProvider;
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub(crate) fn default_provider() -> Arc<dyn DeviceKeyProvider> {
|
||||
Arc::new(unsupported::UnsupportedDeviceKeyProvider)
|
||||
}
|
||||
|
||||
impl DeviceKeyProvider for UnsupportedDeviceKeyProvider {
|
||||
fn create(&self, request: ProviderCreateRequest) -> Result<DeviceKeyInfo, DeviceKeyError> {
|
||||
let _ = request.key_id_for(DeviceKeyProtectionClass::HardwareTpm);
|
||||
let _ = request
|
||||
.protection_policy
|
||||
.allows(DeviceKeyProtectionClass::HardwareTpm);
|
||||
Err(DeviceKeyError::HardwareBackedKeysUnavailable)
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
mod unsupported {
|
||||
use crate::DeviceKeyError;
|
||||
use crate::DeviceKeyInfo;
|
||||
use crate::DeviceKeyProtectionClass;
|
||||
use crate::DeviceKeyProvider;
|
||||
use crate::ProviderCreateRequest;
|
||||
use crate::ProviderSignature;
|
||||
|
||||
fn delete(
|
||||
&self,
|
||||
_key_id: &str,
|
||||
_protection_class: DeviceKeyProtectionClass,
|
||||
) -> Result<(), DeviceKeyError> {
|
||||
Ok(())
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct UnsupportedDeviceKeyProvider;
|
||||
|
||||
fn get_public(
|
||||
&self,
|
||||
_key_id: &str,
|
||||
_protection_class: DeviceKeyProtectionClass,
|
||||
) -> Result<DeviceKeyInfo, DeviceKeyError> {
|
||||
Err(DeviceKeyError::KeyNotFound)
|
||||
}
|
||||
impl DeviceKeyProvider for UnsupportedDeviceKeyProvider {
|
||||
fn create(&self, request: ProviderCreateRequest) -> Result<DeviceKeyInfo, DeviceKeyError> {
|
||||
let _ = request.key_id_for(DeviceKeyProtectionClass::HardwareTpm);
|
||||
let _ = request
|
||||
.protection_policy
|
||||
.allows(DeviceKeyProtectionClass::HardwareTpm);
|
||||
Err(DeviceKeyError::HardwareBackedKeysUnavailable)
|
||||
}
|
||||
|
||||
fn sign(
|
||||
&self,
|
||||
_key_id: &str,
|
||||
_protection_class: DeviceKeyProtectionClass,
|
||||
_payload: &[u8],
|
||||
) -> Result<ProviderSignature, DeviceKeyError> {
|
||||
Err(DeviceKeyError::KeyNotFound)
|
||||
fn delete(
|
||||
&self,
|
||||
_key_id: &str,
|
||||
_protection_class: DeviceKeyProtectionClass,
|
||||
) -> Result<(), DeviceKeyError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_public(
|
||||
&self,
|
||||
_key_id: &str,
|
||||
_protection_class: DeviceKeyProtectionClass,
|
||||
) -> Result<DeviceKeyInfo, DeviceKeyError> {
|
||||
Err(DeviceKeyError::KeyNotFound)
|
||||
}
|
||||
|
||||
fn sign(
|
||||
&self,
|
||||
_key_id: &str,
|
||||
_protection_class: DeviceKeyProtectionClass,
|
||||
_payload: &[u8],
|
||||
) -> Result<ProviderSignature, DeviceKeyError> {
|
||||
Err(DeviceKeyError::KeyNotFound)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
261
codex-rs/device-key/src/platform/macos.rs
Normal file
261
codex-rs/device-key/src/platform/macos.rs
Normal file
@@ -0,0 +1,261 @@
|
||||
use crate::DeviceKeyAlgorithm;
|
||||
use crate::DeviceKeyError;
|
||||
use crate::DeviceKeyInfo;
|
||||
use crate::DeviceKeyProtectionClass;
|
||||
use crate::DeviceKeyProvider;
|
||||
use crate::ProviderCreateRequest;
|
||||
use crate::ProviderSignature;
|
||||
use crate::sec1_public_key_to_spki_der;
|
||||
use std::ffi::CStr;
|
||||
use std::ffi::CString;
|
||||
use std::ffi::c_char;
|
||||
use std::ffi::c_int;
|
||||
use std::ptr;
|
||||
use std::slice;
|
||||
|
||||
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 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);
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct MacOsDeviceKeyProvider;
|
||||
|
||||
impl DeviceKeyProvider for MacOsDeviceKeyProvider {
|
||||
fn create(&self, request: ProviderCreateRequest) -> Result<DeviceKeyInfo, DeviceKeyError> {
|
||||
let secure_enclave_key_id =
|
||||
request.key_id_for(DeviceKeyProtectionClass::HardwareSecureEnclave);
|
||||
match create_or_load_key_info(&secure_enclave_key_id, MacKeyClass::SecureEnclave) {
|
||||
Ok(info) => Ok(info),
|
||||
Err(secure_enclave_error) => {
|
||||
if !matches!(
|
||||
secure_enclave_error,
|
||||
DeviceKeyError::HardwareBackedKeysUnavailable
|
||||
) {
|
||||
return Err(secure_enclave_error);
|
||||
}
|
||||
if !request
|
||||
.protection_policy
|
||||
.allows(DeviceKeyProtectionClass::OsProtectedNonextractable)
|
||||
{
|
||||
return Err(DeviceKeyError::DegradedProtectionNotAllowed {
|
||||
available: DeviceKeyProtectionClass::OsProtectedNonextractable,
|
||||
});
|
||||
}
|
||||
let fallback_key_id =
|
||||
request.key_id_for(DeviceKeyProtectionClass::OsProtectedNonextractable);
|
||||
create_or_load_key_info(&fallback_key_id, MacKeyClass::OsProtectedNonextractable)
|
||||
.map_err(|fallback_error| {
|
||||
DeviceKeyError::Platform(format!(
|
||||
"Secure Enclave key creation failed ({secure_enclave_error}); OS-protected fallback failed ({fallback_error})"
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
protection_class: DeviceKeyProtectionClass,
|
||||
) -> Result<DeviceKeyInfo, DeviceKeyError> {
|
||||
let class = MacKeyClass::from_protection_class(protection_class)
|
||||
.ok_or(DeviceKeyError::KeyNotFound)?;
|
||||
let public_key = load_public_key(key_id, class)?;
|
||||
key_info(key_id, class, public_key.as_slice())
|
||||
}
|
||||
|
||||
fn sign(
|
||||
&self,
|
||||
key_id: &str,
|
||||
protection_class: DeviceKeyProtectionClass,
|
||||
payload: &[u8],
|
||||
) -> Result<ProviderSignature, DeviceKeyError> {
|
||||
let class = MacKeyClass::from_protection_class(protection_class)
|
||||
.ok_or(DeviceKeyError::KeyNotFound)?;
|
||||
let signature_der = sign(key_id, class, payload)?;
|
||||
Ok(ProviderSignature {
|
||||
signature_der,
|
||||
algorithm: DeviceKeyAlgorithm::EcdsaP256Sha256,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum MacKeyClass {
|
||||
SecureEnclave,
|
||||
OsProtectedNonextractable,
|
||||
}
|
||||
|
||||
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,
|
||||
Self::OsProtectedNonextractable => DeviceKeyProtectionClass::OsProtectedNonextractable,
|
||||
}
|
||||
}
|
||||
|
||||
fn tag_prefix(self) -> &'static str {
|
||||
match self {
|
||||
Self::SecureEnclave => "secure-enclave",
|
||||
Self::OsProtectedNonextractable => "os-protected-nonextractable",
|
||||
}
|
||||
}
|
||||
|
||||
fn from_protection_class(protection_class: DeviceKeyProtectionClass) -> Option<Self> {
|
||||
match protection_class {
|
||||
DeviceKeyProtectionClass::HardwareSecureEnclave => Some(Self::SecureEnclave),
|
||||
DeviceKeyProtectionClass::OsProtectedNonextractable => {
|
||||
Some(Self::OsProtectedNonextractable)
|
||||
}
|
||||
DeviceKeyProtectionClass::HardwareTpm => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_or_load_key_info(
|
||||
key_id: &str,
|
||||
class: MacKeyClass,
|
||||
) -> Result<DeviceKeyInfo, DeviceKeyError> {
|
||||
let public_key = create_or_load_public_key(key_id, class)?;
|
||||
key_info(key_id, class, public_key.as_slice())
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
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 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(|_| ())
|
||||
}
|
||||
|
||||
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,
|
||||
sec1_public_key: &[u8],
|
||||
) -> Result<DeviceKeyInfo, DeviceKeyError> {
|
||||
Ok(DeviceKeyInfo {
|
||||
key_id: key_id.to_string(),
|
||||
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.{}.{}",
|
||||
class.tag_prefix(),
|
||||
key_id
|
||||
)
|
||||
}
|
||||
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
|
||||
385
codex-rs/device-key/src/platform/macos_provider.m
Normal file
385
codex-rs/device-key/src/platform/macos_provider.m
Normal file
@@ -0,0 +1,385 @@
|
||||
#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 OSStatus const CodexDeviceKeyErrSecMissingEntitlement = -34018;
|
||||
|
||||
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 BOOL CodexDeviceKeyMacSecureEnclaveUnavailableStatus(OSStatus status) {
|
||||
return status == errSecUnimplemented ||
|
||||
status == errSecParam;
|
||||
}
|
||||
|
||||
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,
|
||||
(__bridge id)kSecUseDataProtectionKeychain: @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)kSecUseDataProtectionKeychain: @YES,
|
||||
(__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);
|
||||
OSStatus code = nsError == nil ? 0 : (OSStatus)nsError.code;
|
||||
// Missing Keychain entitlements affect both Secure Enclave and OS-protected permanent keys, so
|
||||
// do not classify this as degraded hardware availability and retry with the fallback class.
|
||||
if (code == CodexDeviceKeyErrSecMissingEntitlement) {
|
||||
*status = CodexDeviceKeyMacStatusPlatformError;
|
||||
*errorMessage = @"macOS Keychain entitlements are required to create persistent device keys";
|
||||
return NULL;
|
||||
}
|
||||
if (keyClass == CodexDeviceKeyMacKeyClassSecureEnclave &&
|
||||
CodexDeviceKeyMacSecureEnclaveUnavailableStatus(code)) {
|
||||
*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;
|
||||
}
|
||||
300
codex-rs/scripts/check-device-key-app-server.py
Executable file
300
codex-rs/scripts/check-device-key-app-server.py
Executable file
@@ -0,0 +1,300 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Exercise app-server device/key/* RPCs against a local Codex binary.
|
||||
|
||||
This is intentionally dependency-free so it can be run against a freshly built
|
||||
and code-signed local CLI:
|
||||
|
||||
python3 codex-rs/scripts/check-device-key-app-server.py \
|
||||
--binary codex-rs/target/debug/codex
|
||||
|
||||
The script uses a temporary CODEX_HOME and spawns `codex app-server --listen
|
||||
stdio://`. It creates a new persistent device key each successful run; the
|
||||
app-server API currently does not expose a delete RPC.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
DEFAULT_TOKEN_SHA256_EMPTY = "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU"
|
||||
|
||||
|
||||
class DeviceKeyRpcError(RuntimeError):
|
||||
def __init__(self, method: str, error: dict[str, Any]) -> None:
|
||||
self.method = method
|
||||
self.error = error
|
||||
super().__init__(f"{method} failed: {error}")
|
||||
|
||||
|
||||
class AppServerClient:
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
binary: Path,
|
||||
codex_home: Path,
|
||||
verbose: bool,
|
||||
) -> None:
|
||||
self._verbose = verbose
|
||||
env = os.environ.copy()
|
||||
env["CODEX_HOME"] = str(codex_home)
|
||||
env["CODEX_APP_SERVER_MANAGED_CONFIG_PATH"] = str(
|
||||
codex_home / "managed_config.toml"
|
||||
)
|
||||
env.setdefault("RUST_LOG", "info")
|
||||
env.pop("CODEX_INTERNAL_ORIGINATOR_OVERRIDE", None)
|
||||
|
||||
self._process = subprocess.Popen(
|
||||
[str(binary), "app-server", "--listen", "stdio://"],
|
||||
cwd=codex_home,
|
||||
env=env,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
bufsize=1,
|
||||
)
|
||||
if self._process.stdin is None or self._process.stdout is None:
|
||||
raise RuntimeError("failed to open app-server stdio pipes")
|
||||
|
||||
self._stdin = self._process.stdin
|
||||
self._stdout = self._process.stdout
|
||||
|
||||
def close(self) -> None:
|
||||
if self._process.poll() is None:
|
||||
self._process.terminate()
|
||||
try:
|
||||
self._process.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
self._process.kill()
|
||||
self._process.wait(timeout=5)
|
||||
stderr = self._process.stderr.read() if self._process.stderr else ""
|
||||
if self._verbose and stderr:
|
||||
print("app-server stderr:", file=sys.stderr)
|
||||
print(stderr, file=sys.stderr, end="" if stderr.endswith("\n") else "\n")
|
||||
|
||||
def send(self, message: dict[str, Any]) -> None:
|
||||
line = json.dumps(message, separators=(",", ":"))
|
||||
if self._verbose:
|
||||
print(f">>> {line}", file=sys.stderr)
|
||||
self._stdin.write(line + "\n")
|
||||
self._stdin.flush()
|
||||
|
||||
def read_until_id(self, request_id: int, timeout_seconds: float) -> dict[str, Any]:
|
||||
deadline = time.monotonic() + timeout_seconds
|
||||
while time.monotonic() < deadline:
|
||||
line = self._stdout.readline()
|
||||
if not line:
|
||||
raise RuntimeError("app-server stdout closed")
|
||||
if self._verbose:
|
||||
print(f"<<< {line.strip()}", file=sys.stderr)
|
||||
message = json.loads(line)
|
||||
if message.get("id") == request_id:
|
||||
return message
|
||||
raise TimeoutError(f"timed out waiting for response id {request_id}")
|
||||
|
||||
def request(
|
||||
self,
|
||||
request_id: int,
|
||||
method: str,
|
||||
params: dict[str, Any] | None,
|
||||
*,
|
||||
timeout_seconds: float,
|
||||
) -> dict[str, Any]:
|
||||
message: dict[str, Any] = {"id": request_id, "method": method}
|
||||
if params is not None:
|
||||
message["params"] = params
|
||||
self.send(message)
|
||||
response = self.read_until_id(request_id, timeout_seconds)
|
||||
if "error" in response:
|
||||
raise DeviceKeyRpcError(method, response["error"])
|
||||
result = response.get("result")
|
||||
if not isinstance(result, dict):
|
||||
raise RuntimeError(f"{method} returned non-object result: {result!r}")
|
||||
return result
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Run app-server device/key/create, device/key/sign, and device/key/public.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--binary",
|
||||
type=Path,
|
||||
default=Path("codex-rs/target/debug/codex"),
|
||||
help="Path to the codex CLI binary to test.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--codex-home",
|
||||
type=Path,
|
||||
help="CODEX_HOME to use. Defaults to a temporary directory.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--keep-codex-home",
|
||||
action="store_true",
|
||||
help="Do not delete the temporary CODEX_HOME after the run.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--protection-policy",
|
||||
choices=("hardware_only", "allow_os_protected_nonextractable"),
|
||||
default="hardware_only",
|
||||
help="Protection policy sent to device/key/create.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--account-user-id",
|
||||
default="acct_local_secure_enclave_check",
|
||||
help="accountUserId used for device/key/create and sign payload.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--client-id",
|
||||
default="cli_local_secure_enclave_check",
|
||||
help="clientId used for device/key/create and sign payload.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--timeout-seconds",
|
||||
type=float,
|
||||
default=120.0,
|
||||
help="Timeout for each app-server request.",
|
||||
)
|
||||
parser.add_argument("--verbose", action="store_true", help="Print JSON-RPC traffic.")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
binary = args.binary.expanduser().resolve()
|
||||
if not binary.is_file():
|
||||
print(f"binary not found: {binary}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
temp_home: tempfile.TemporaryDirectory[str] | None = None
|
||||
if args.codex_home:
|
||||
codex_home = args.codex_home.expanduser().resolve()
|
||||
codex_home.mkdir(parents=True, exist_ok=True)
|
||||
else:
|
||||
temp_home = tempfile.TemporaryDirectory(prefix="codex-device-key-check-")
|
||||
codex_home = Path(temp_home.name)
|
||||
|
||||
client = AppServerClient(binary=binary, codex_home=codex_home, verbose=args.verbose)
|
||||
try:
|
||||
init_result = client.request(
|
||||
1,
|
||||
"initialize",
|
||||
{
|
||||
"clientInfo": {
|
||||
"name": "device-key-local-signing-check",
|
||||
"version": "0.1.0",
|
||||
},
|
||||
"capabilities": {"experimentalApi": True},
|
||||
},
|
||||
timeout_seconds=args.timeout_seconds,
|
||||
)
|
||||
client.send({"method": "initialized"})
|
||||
|
||||
created = client.request(
|
||||
2,
|
||||
"device/key/create",
|
||||
{
|
||||
"accountUserId": args.account_user_id,
|
||||
"clientId": args.client_id,
|
||||
"protectionPolicy": args.protection_policy,
|
||||
},
|
||||
timeout_seconds=args.timeout_seconds,
|
||||
)
|
||||
|
||||
sign_payload = {
|
||||
"type": "remoteControlClientConnection",
|
||||
"nonce": "nonce-local-secure-enclave-check",
|
||||
"audience": "remote_control_client_websocket",
|
||||
"sessionId": "wssess_local_secure_enclave_check",
|
||||
"targetOrigin": "https://chatgpt.com",
|
||||
"targetPath": "/api/codex/remote/control/client",
|
||||
"accountUserId": args.account_user_id,
|
||||
"clientId": args.client_id,
|
||||
"tokenSha256Base64url": DEFAULT_TOKEN_SHA256_EMPTY,
|
||||
"tokenExpiresAt": 4_102_444_800,
|
||||
"scopes": ["remote_control_controller_websocket"],
|
||||
}
|
||||
signed = client.request(
|
||||
3,
|
||||
"device/key/sign",
|
||||
{"keyId": created["keyId"], "payload": sign_payload},
|
||||
timeout_seconds=args.timeout_seconds,
|
||||
)
|
||||
|
||||
public = client.request(
|
||||
4,
|
||||
"device/key/public",
|
||||
{"keyId": created["keyId"]},
|
||||
timeout_seconds=args.timeout_seconds,
|
||||
)
|
||||
|
||||
summary = {
|
||||
"binary": str(binary),
|
||||
"codexHome": str(codex_home),
|
||||
"initialize": {
|
||||
"platformOs": init_result.get("platformOs"),
|
||||
"userAgent": init_result.get("userAgent"),
|
||||
},
|
||||
"create": {
|
||||
"keyId": created.get("keyId"),
|
||||
"algorithm": created.get("algorithm"),
|
||||
"protectionClass": created.get("protectionClass"),
|
||||
"publicKeySpkiDerBase64Length": len(
|
||||
created.get("publicKeySpkiDerBase64", "")
|
||||
),
|
||||
},
|
||||
"sign": {
|
||||
"algorithm": signed.get("algorithm"),
|
||||
"signatureDerBase64Length": len(signed.get("signatureDerBase64", "")),
|
||||
"signedPayloadBase64Length": len(signed.get("signedPayloadBase64", "")),
|
||||
},
|
||||
"public": {
|
||||
"keyIdMatchesCreate": public.get("keyId") == created.get("keyId"),
|
||||
"algorithm": public.get("algorithm"),
|
||||
"protectionClass": public.get("protectionClass"),
|
||||
"publicKeyMatchesCreate": public.get("publicKeySpkiDerBase64")
|
||||
== created.get("publicKeySpkiDerBase64"),
|
||||
},
|
||||
}
|
||||
print(json.dumps(summary, indent=2, sort_keys=True))
|
||||
|
||||
if created.get("protectionClass") != "hardware_secure_enclave":
|
||||
print(
|
||||
"device/key/create did not return hardware_secure_enclave",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 3
|
||||
return 0
|
||||
except DeviceKeyRpcError as exc:
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"binary": str(binary),
|
||||
"codexHome": str(codex_home),
|
||||
"failedMethod": exc.method,
|
||||
"error": exc.error,
|
||||
},
|
||||
indent=2,
|
||||
sort_keys=True,
|
||||
)
|
||||
)
|
||||
return 1
|
||||
finally:
|
||||
client.close()
|
||||
if temp_home is not None and not args.keep_codex_home:
|
||||
temp_home.cleanup()
|
||||
elif temp_home is not None:
|
||||
print(f"kept CODEX_HOME at {codex_home}", file=sys.stderr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user