mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
app-server: implement device key v2 methods (#18430)
## Why The device-key protocol needs an app-server implementation that keeps local key operations behind the same request-processing boundary as other v2 APIs. app-server owns request dispatch, transport policy, documentation, and JSON-RPC error shaping. `codex-device-key` owns key binding, validation, platform provider selection, and signing mechanics. Keeping the adapter thin makes the boundary easier to review and avoids moving local key-management details into thread orchestration code. ## What changed - Added `DeviceKeyApi` as the app-server adapter around `DeviceKeyStore`. - Converted protocol protection policies, payload variants, algorithms, and protection classes to and from the device-key crate types. - Encoded SPKI public keys and DER signatures as base64 protocol fields. - Routed `device/key/create`, `device/key/public`, and `device/key/sign` through `MessageProcessor`. - Rejected remote transports before provider access while allowing local `stdio` and in-process callers to reach the device-key API. - Added stdio, in-process, and websocket tests for device-key validation and transport policy. - Documented the device-key methods in the app-server v2 method list. ## Test coverage - `device_key_create_rejects_empty_account_user_id` - `in_process_allows_device_key_requests_to_reach_device_key_api` - `device_key_methods_are_rejected_over_websocket` ## Stack This is PR 3 of 4 in the device-key app-server stack. It is stacked on #18429. ## Validation - `cargo test -p codex-app-server device_key` - `just fix -p codex-app-server`
This commit is contained in:
1 parent
e502f0b52d
commit
69c3d12274
17 files changed
+734
-6
No files matched your search
@@ -63,6 +63,7 @@ use crate::outgoing_message::OutgoingMessage;
|
||||
use crate::outgoing_message::OutgoingMessageSender;
|
||||
use crate::outgoing_message::QueuedOutgoingMessage;
|
||||
use crate::transport::CHANNEL_CAPACITY;
|
||||
use crate::transport::ConnectionOrigin;
|
||||
use crate::transport::OutboundConnectionState;
|
||||
use crate::transport::route_outgoing_envelope;
|
||||
use codex_analytics::AppServerRpcTransport;
|
||||
@@ -416,7 +417,7 @@ fn start_uninitialized(args: InProcessStartArgs) -> InProcessClientHandle {
|
||||
remote_control_handle: None,
|
||||
}));
|
||||
let mut thread_created_rx = processor.thread_created_receiver();
|
||||
let session = Arc::new(ConnectionSessionState::default());
|
||||
let session = Arc::new(ConnectionSessionState::new(ConnectionOrigin::InProcess));
|
||||
let mut listen_for_threads = true;
|
||||
|
||||
loop {
|
||||
@@ -713,6 +714,11 @@ mod tests {
|
||||
use super::*;
|
||||
use codex_app_server_protocol::ClientInfo;
|
||||
use codex_app_server_protocol::ConfigRequirementsReadResponse;
|
||||
use codex_app_server_protocol::DeviceKeyPublicParams;
|
||||
use codex_app_server_protocol::DeviceKeySignParams;
|
||||
use codex_app_server_protocol::DeviceKeySignPayload;
|
||||
use codex_app_server_protocol::RemoteControlClientConnectionAudience;
|
||||
use codex_app_server_protocol::RemoteControlClientEnrollmentAudience;
|
||||
use codex_app_server_protocol::SessionSource as ApiSessionSource;
|
||||
use codex_app_server_protocol::ThreadStartParams;
|
||||
use codex_app_server_protocol::ThreadStartResponse;
|
||||
@@ -786,6 +792,87 @@ mod tests {
|
||||
.expect("in-process runtime should shutdown cleanly");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn in_process_allows_device_key_requests_to_reach_device_key_api() {
|
||||
let client = start_test_client(SessionSource::Cli).await;
|
||||
const MALFORMED_KEY_ID_MESSAGE: &str = concat!(
|
||||
"invalid device key payload: keyId must be dk_hse_, dk_tpm_, or dk_osn_ ",
|
||||
"followed by unpadded base64url-encoded 32 bytes"
|
||||
);
|
||||
let requests = [
|
||||
(
|
||||
ClientRequest::DeviceKeyPublic {
|
||||
request_id: RequestId::Integer(11),
|
||||
params: DeviceKeyPublicParams {
|
||||
key_id: String::new(),
|
||||
},
|
||||
},
|
||||
MALFORMED_KEY_ID_MESSAGE,
|
||||
),
|
||||
(
|
||||
ClientRequest::DeviceKeySign {
|
||||
request_id: RequestId::Integer(12),
|
||||
params: DeviceKeySignParams {
|
||||
key_id: String::new(),
|
||||
payload: DeviceKeySignPayload::RemoteControlClientConnection {
|
||||
nonce: "nonce-123".to_string(),
|
||||
audience:
|
||||
RemoteControlClientConnectionAudience::RemoteControlClientWebsocket,
|
||||
session_id: "wssess_123".to_string(),
|
||||
target_origin: "https://chatgpt.com".to_string(),
|
||||
target_path: "/api/codex/remote/control/client".to_string(),
|
||||
account_user_id: "acct_123".to_string(),
|
||||
client_id: "cli_123".to_string(),
|
||||
token_expires_at: 4_102_444_800,
|
||||
token_sha256_base64url: "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU"
|
||||
.to_string(),
|
||||
scopes: vec!["remote_control_controller_websocket".to_string()],
|
||||
},
|
||||
},
|
||||
},
|
||||
MALFORMED_KEY_ID_MESSAGE,
|
||||
),
|
||||
(
|
||||
ClientRequest::DeviceKeySign {
|
||||
request_id: RequestId::Integer(13),
|
||||
params: DeviceKeySignParams {
|
||||
key_id: String::new(),
|
||||
payload: DeviceKeySignPayload::RemoteControlClientEnrollment {
|
||||
nonce: "nonce-123".to_string(),
|
||||
audience:
|
||||
RemoteControlClientEnrollmentAudience::RemoteControlClientEnrollment,
|
||||
challenge_id: "rch_123".to_string(),
|
||||
target_origin: "https://chatgpt.com".to_string(),
|
||||
target_path: "/wham/remote/control/client/enroll".to_string(),
|
||||
account_user_id: "acct_123".to_string(),
|
||||
client_id: "cli_123".to_string(),
|
||||
device_identity_sha256_base64url:
|
||||
"47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU".to_string(),
|
||||
challenge_expires_at: 4_102_444_800,
|
||||
},
|
||||
},
|
||||
},
|
||||
MALFORMED_KEY_ID_MESSAGE,
|
||||
),
|
||||
];
|
||||
|
||||
for (request, expected_message) in requests {
|
||||
let error = client
|
||||
.request(request)
|
||||
.await
|
||||
.expect("request transport should work")
|
||||
.expect_err("request should be rejected");
|
||||
|
||||
assert_eq!(error.code, INVALID_REQUEST_ERROR_CODE);
|
||||
assert_eq!(error.message, expected_message);
|
||||
}
|
||||
|
||||
client
|
||||
.shutdown()
|
||||
.await
|
||||
.expect("in-process runtime should shutdown cleanly");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn in_process_start_uses_requested_session_source_for_thread_start() {
|
||||
for (requested_source, expected_source) in [
|
||||
|
||||
Reference in new issue
Block a user