diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index 43871d74a..352cf0211 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -521,6 +521,200 @@ } ] }, + "DeviceKeyCreateParams": { + "description": "Create a controller-local device key with a random key id.", + "properties": { + "accountUserId": { + "type": "string" + }, + "clientId": { + "type": "string" + }, + "protectionPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/DeviceKeyProtectionPolicy" + }, + { + "type": "null" + } + ], + "description": "Defaults to `hardware_only` when omitted." + } + }, + "required": [ + "accountUserId", + "clientId" + ], + "type": "object" + }, + "DeviceKeyProtectionPolicy": { + "description": "Protection policy for creating or loading a controller-local device key.", + "enum": [ + "hardware_only", + "allow_os_protected_nonextractable" + ], + "type": "string" + }, + "DeviceKeyPublicParams": { + "description": "Fetch a controller-local device key public key by id.", + "properties": { + "keyId": { + "type": "string" + } + }, + "required": [ + "keyId" + ], + "type": "object" + }, + "DeviceKeySignParams": { + "description": "Sign an accepted structured payload with a controller-local device key.", + "properties": { + "keyId": { + "type": "string" + }, + "payload": { + "$ref": "#/definitions/DeviceKeySignPayload" + } + }, + "required": [ + "keyId", + "payload" + ], + "type": "object" + }, + "DeviceKeySignPayload": { + "description": "Structured payloads accepted by `device/key/sign`.", + "oneOf": [ + { + "description": "Payload bound to one remote-control controller websocket `/client` connection challenge.", + "properties": { + "accountUserId": { + "type": "string" + }, + "audience": { + "$ref": "#/definitions/RemoteControlClientConnectionAudience" + }, + "clientId": { + "type": "string" + }, + "nonce": { + "type": "string" + }, + "scopes": { + "description": "Must contain exactly `remote_control_controller_websocket`.", + "items": { + "type": "string" + }, + "type": "array" + }, + "sessionId": { + "description": "Backend-issued websocket session id that this proof authorizes.", + "type": "string" + }, + "targetOrigin": { + "description": "Origin of the backend endpoint that issued the challenge and will verify this proof.", + "type": "string" + }, + "targetPath": { + "description": "Websocket route path that this proof authorizes.", + "type": "string" + }, + "tokenExpiresAt": { + "description": "Remote-control token expiration as Unix seconds.", + "format": "int64", + "type": "integer" + }, + "tokenSha256Base64url": { + "description": "SHA-256 of the controller-scoped remote-control token, encoded as unpadded base64url.", + "type": "string" + }, + "type": { + "enum": [ + "remoteControlClientConnection" + ], + "title": "RemoteControlClientConnectionDeviceKeySignPayloadType", + "type": "string" + } + }, + "required": [ + "accountUserId", + "audience", + "clientId", + "nonce", + "scopes", + "sessionId", + "targetOrigin", + "targetPath", + "tokenExpiresAt", + "tokenSha256Base64url", + "type" + ], + "title": "RemoteControlClientConnectionDeviceKeySignPayload", + "type": "object" + }, + { + "description": "Payload bound to a remote-control client `/client/enroll` ownership challenge.", + "properties": { + "accountUserId": { + "type": "string" + }, + "audience": { + "$ref": "#/definitions/RemoteControlClientEnrollmentAudience" + }, + "challengeExpiresAt": { + "description": "Enrollment challenge expiration as Unix seconds.", + "format": "int64", + "type": "integer" + }, + "challengeId": { + "description": "Backend-issued enrollment challenge id that this proof authorizes.", + "type": "string" + }, + "clientId": { + "type": "string" + }, + "deviceIdentitySha256Base64url": { + "description": "SHA-256 of the requested device identity operation, encoded as unpadded base64url.", + "type": "string" + }, + "nonce": { + "type": "string" + }, + "targetOrigin": { + "description": "Origin of the backend endpoint that issued the challenge and will verify this proof.", + "type": "string" + }, + "targetPath": { + "description": "HTTP route path that this proof authorizes.", + "type": "string" + }, + "type": { + "enum": [ + "remoteControlClientEnrollment" + ], + "title": "RemoteControlClientEnrollmentDeviceKeySignPayloadType", + "type": "string" + } + }, + "required": [ + "accountUserId", + "audience", + "challengeExpiresAt", + "challengeId", + "clientId", + "deviceIdentitySha256Base64url", + "nonce", + "targetOrigin", + "targetPath", + "type" + ], + "title": "RemoteControlClientEnrollmentDeviceKeySignPayload", + "type": "object" + } + ] + }, "DynamicToolSpec": { "properties": { "deferLoading": { @@ -1747,6 +1941,20 @@ } ] }, + "RemoteControlClientConnectionAudience": { + "description": "Audience for a remote-control client connection device-key proof.", + "enum": [ + "remote_control_client_websocket" + ], + "type": "string" + }, + "RemoteControlClientEnrollmentAudience": { + "description": "Audience for a remote-control client enrollment device-key proof.", + "enum": [ + "remote_control_client_enrollment" + ], + "type": "string" + }, "RequestId": { "anyOf": [ { @@ -4360,6 +4568,78 @@ "title": "App/listRequest", "type": "object" }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "device/key/create" + ], + "title": "Device/key/createRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/DeviceKeyCreateParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Device/key/createRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "device/key/public" + ], + "title": "Device/key/publicRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/DeviceKeyPublicParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Device/key/publicRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "device/key/sign" + ], + "title": "Device/key/signRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/DeviceKeySignParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Device/key/signRequest", + "type": "object" + }, { "properties": { "id": { diff --git a/codex-rs/app-server-protocol/schema/json/DynamicToolCallParams.json b/codex-rs/app-server-protocol/schema/json/DynamicToolCallParams.json index 0aa8b9c57..991733da6 100644 --- a/codex-rs/app-server-protocol/schema/json/DynamicToolCallParams.json +++ b/codex-rs/app-server-protocol/schema/json/DynamicToolCallParams.json @@ -30,4 +30,4 @@ ], "title": "DynamicToolCallParams", "type": "object" -} +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index 2cb5fb84d..630e8d493 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -737,6 +737,78 @@ "title": "App/listRequest", "type": "object" }, + { + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "enum": [ + "device/key/create" + ], + "title": "Device/key/createRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/DeviceKeyCreateParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Device/key/createRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "enum": [ + "device/key/public" + ], + "title": "Device/key/publicRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/DeviceKeyPublicParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Device/key/publicRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "enum": [ + "device/key/sign" + ], + "title": "Device/key/signRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/DeviceKeySignParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Device/key/signRequest", + "type": "object" + }, { "properties": { "id": { @@ -7324,6 +7396,300 @@ "title": "DeprecationNoticeNotification", "type": "object" }, + "DeviceKeyAlgorithm": { + "description": "Device-key algorithm reported at enrollment and signing boundaries.", + "enum": [ + "ecdsa_p256_sha256" + ], + "type": "string" + }, + "DeviceKeyCreateParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Create a controller-local device key with a random key id.", + "properties": { + "accountUserId": { + "type": "string" + }, + "clientId": { + "type": "string" + }, + "protectionPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/v2/DeviceKeyProtectionPolicy" + }, + { + "type": "null" + } + ], + "description": "Defaults to `hardware_only` when omitted." + } + }, + "required": [ + "accountUserId", + "clientId" + ], + "title": "DeviceKeyCreateParams", + "type": "object" + }, + "DeviceKeyCreateResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Device-key metadata and public key returned by create/public APIs.", + "properties": { + "algorithm": { + "$ref": "#/definitions/v2/DeviceKeyAlgorithm" + }, + "keyId": { + "type": "string" + }, + "protectionClass": { + "$ref": "#/definitions/v2/DeviceKeyProtectionClass" + }, + "publicKeySpkiDerBase64": { + "description": "SubjectPublicKeyInfo DER encoded as base64.", + "type": "string" + } + }, + "required": [ + "algorithm", + "keyId", + "protectionClass", + "publicKeySpkiDerBase64" + ], + "title": "DeviceKeyCreateResponse", + "type": "object" + }, + "DeviceKeyProtectionClass": { + "description": "Platform protection class for a controller-local device key.", + "enum": [ + "hardware_secure_enclave", + "hardware_tpm", + "os_protected_nonextractable" + ], + "type": "string" + }, + "DeviceKeyProtectionPolicy": { + "description": "Protection policy for creating or loading a controller-local device key.", + "enum": [ + "hardware_only", + "allow_os_protected_nonextractable" + ], + "type": "string" + }, + "DeviceKeyPublicParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Fetch a controller-local device key public key by id.", + "properties": { + "keyId": { + "type": "string" + } + }, + "required": [ + "keyId" + ], + "title": "DeviceKeyPublicParams", + "type": "object" + }, + "DeviceKeyPublicResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Device-key public metadata returned by `device/key/public`.", + "properties": { + "algorithm": { + "$ref": "#/definitions/v2/DeviceKeyAlgorithm" + }, + "keyId": { + "type": "string" + }, + "protectionClass": { + "$ref": "#/definitions/v2/DeviceKeyProtectionClass" + }, + "publicKeySpkiDerBase64": { + "description": "SubjectPublicKeyInfo DER encoded as base64.", + "type": "string" + } + }, + "required": [ + "algorithm", + "keyId", + "protectionClass", + "publicKeySpkiDerBase64" + ], + "title": "DeviceKeyPublicResponse", + "type": "object" + }, + "DeviceKeySignParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Sign an accepted structured payload with a controller-local device key.", + "properties": { + "keyId": { + "type": "string" + }, + "payload": { + "$ref": "#/definitions/v2/DeviceKeySignPayload" + } + }, + "required": [ + "keyId", + "payload" + ], + "title": "DeviceKeySignParams", + "type": "object" + }, + "DeviceKeySignPayload": { + "description": "Structured payloads accepted by `device/key/sign`.", + "oneOf": [ + { + "description": "Payload bound to one remote-control controller websocket `/client` connection challenge.", + "properties": { + "accountUserId": { + "type": "string" + }, + "audience": { + "$ref": "#/definitions/v2/RemoteControlClientConnectionAudience" + }, + "clientId": { + "type": "string" + }, + "nonce": { + "type": "string" + }, + "scopes": { + "description": "Must contain exactly `remote_control_controller_websocket`.", + "items": { + "type": "string" + }, + "type": "array" + }, + "sessionId": { + "description": "Backend-issued websocket session id that this proof authorizes.", + "type": "string" + }, + "targetOrigin": { + "description": "Origin of the backend endpoint that issued the challenge and will verify this proof.", + "type": "string" + }, + "targetPath": { + "description": "Websocket route path that this proof authorizes.", + "type": "string" + }, + "tokenExpiresAt": { + "description": "Remote-control token expiration as Unix seconds.", + "format": "int64", + "type": "integer" + }, + "tokenSha256Base64url": { + "description": "SHA-256 of the controller-scoped remote-control token, encoded as unpadded base64url.", + "type": "string" + }, + "type": { + "enum": [ + "remoteControlClientConnection" + ], + "title": "RemoteControlClientConnectionDeviceKeySignPayloadType", + "type": "string" + } + }, + "required": [ + "accountUserId", + "audience", + "clientId", + "nonce", + "scopes", + "sessionId", + "targetOrigin", + "targetPath", + "tokenExpiresAt", + "tokenSha256Base64url", + "type" + ], + "title": "RemoteControlClientConnectionDeviceKeySignPayload", + "type": "object" + }, + { + "description": "Payload bound to a remote-control client `/client/enroll` ownership challenge.", + "properties": { + "accountUserId": { + "type": "string" + }, + "audience": { + "$ref": "#/definitions/v2/RemoteControlClientEnrollmentAudience" + }, + "challengeExpiresAt": { + "description": "Enrollment challenge expiration as Unix seconds.", + "format": "int64", + "type": "integer" + }, + "challengeId": { + "description": "Backend-issued enrollment challenge id that this proof authorizes.", + "type": "string" + }, + "clientId": { + "type": "string" + }, + "deviceIdentitySha256Base64url": { + "description": "SHA-256 of the requested device identity operation, encoded as unpadded base64url.", + "type": "string" + }, + "nonce": { + "type": "string" + }, + "targetOrigin": { + "description": "Origin of the backend endpoint that issued the challenge and will verify this proof.", + "type": "string" + }, + "targetPath": { + "description": "HTTP route path that this proof authorizes.", + "type": "string" + }, + "type": { + "enum": [ + "remoteControlClientEnrollment" + ], + "title": "RemoteControlClientEnrollmentDeviceKeySignPayloadType", + "type": "string" + } + }, + "required": [ + "accountUserId", + "audience", + "challengeExpiresAt", + "challengeId", + "clientId", + "deviceIdentitySha256Base64url", + "nonce", + "targetOrigin", + "targetPath", + "type" + ], + "title": "RemoteControlClientEnrollmentDeviceKeySignPayload", + "type": "object" + } + ] + }, + "DeviceKeySignResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "ASN.1 DER signature returned by `device/key/sign`.", + "properties": { + "algorithm": { + "$ref": "#/definitions/v2/DeviceKeyAlgorithm" + }, + "signatureDerBase64": { + "description": "ECDSA signature DER encoded as base64.", + "type": "string" + }, + "signedPayloadBase64": { + "description": "Exact bytes signed by the device key, encoded as base64. Verifiers must verify this byte string directly and must not reserialize `payload`.", + "type": "string" + } + }, + "required": [ + "algorithm", + "signatureDerBase64", + "signedPayloadBase64" + ], + "title": "DeviceKeySignResponse", + "type": "object" + }, "DynamicToolCallOutputContentItem": { "oneOf": [ { @@ -11642,6 +12008,20 @@ "title": "ReasoningTextDeltaNotification", "type": "object" }, + "RemoteControlClientConnectionAudience": { + "description": "Audience for a remote-control client connection device-key proof.", + "enum": [ + "remote_control_client_websocket" + ], + "type": "string" + }, + "RemoteControlClientEnrollmentAudience": { + "description": "Audience for a remote-control client enrollment device-key proof.", + "enum": [ + "remote_control_client_enrollment" + ], + "type": "string" + }, "RequestId": { "anyOf": [ { @@ -14099,8 +14479,8 @@ }, "namespace": { "type": [ - "null", - "string" + "string", + "null" ] }, "status": { @@ -16612,4 +16992,4 @@ }, "title": "CodexAppServerProtocol", "type": "object" -} +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index 4ff918e64..82c990533 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -1418,6 +1418,78 @@ "title": "App/listRequest", "type": "object" }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "device/key/create" + ], + "title": "Device/key/createRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/DeviceKeyCreateParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Device/key/createRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "device/key/public" + ], + "title": "Device/key/publicRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/DeviceKeyPublicParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Device/key/publicRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "device/key/sign" + ], + "title": "Device/key/signRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/DeviceKeySignParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Device/key/signRequest", + "type": "object" + }, { "properties": { "id": { @@ -3935,6 +4007,300 @@ "title": "DeprecationNoticeNotification", "type": "object" }, + "DeviceKeyAlgorithm": { + "description": "Device-key algorithm reported at enrollment and signing boundaries.", + "enum": [ + "ecdsa_p256_sha256" + ], + "type": "string" + }, + "DeviceKeyCreateParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Create a controller-local device key with a random key id.", + "properties": { + "accountUserId": { + "type": "string" + }, + "clientId": { + "type": "string" + }, + "protectionPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/DeviceKeyProtectionPolicy" + }, + { + "type": "null" + } + ], + "description": "Defaults to `hardware_only` when omitted." + } + }, + "required": [ + "accountUserId", + "clientId" + ], + "title": "DeviceKeyCreateParams", + "type": "object" + }, + "DeviceKeyCreateResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Device-key metadata and public key returned by create/public APIs.", + "properties": { + "algorithm": { + "$ref": "#/definitions/DeviceKeyAlgorithm" + }, + "keyId": { + "type": "string" + }, + "protectionClass": { + "$ref": "#/definitions/DeviceKeyProtectionClass" + }, + "publicKeySpkiDerBase64": { + "description": "SubjectPublicKeyInfo DER encoded as base64.", + "type": "string" + } + }, + "required": [ + "algorithm", + "keyId", + "protectionClass", + "publicKeySpkiDerBase64" + ], + "title": "DeviceKeyCreateResponse", + "type": "object" + }, + "DeviceKeyProtectionClass": { + "description": "Platform protection class for a controller-local device key.", + "enum": [ + "hardware_secure_enclave", + "hardware_tpm", + "os_protected_nonextractable" + ], + "type": "string" + }, + "DeviceKeyProtectionPolicy": { + "description": "Protection policy for creating or loading a controller-local device key.", + "enum": [ + "hardware_only", + "allow_os_protected_nonextractable" + ], + "type": "string" + }, + "DeviceKeyPublicParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Fetch a controller-local device key public key by id.", + "properties": { + "keyId": { + "type": "string" + } + }, + "required": [ + "keyId" + ], + "title": "DeviceKeyPublicParams", + "type": "object" + }, + "DeviceKeyPublicResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Device-key public metadata returned by `device/key/public`.", + "properties": { + "algorithm": { + "$ref": "#/definitions/DeviceKeyAlgorithm" + }, + "keyId": { + "type": "string" + }, + "protectionClass": { + "$ref": "#/definitions/DeviceKeyProtectionClass" + }, + "publicKeySpkiDerBase64": { + "description": "SubjectPublicKeyInfo DER encoded as base64.", + "type": "string" + } + }, + "required": [ + "algorithm", + "keyId", + "protectionClass", + "publicKeySpkiDerBase64" + ], + "title": "DeviceKeyPublicResponse", + "type": "object" + }, + "DeviceKeySignParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Sign an accepted structured payload with a controller-local device key.", + "properties": { + "keyId": { + "type": "string" + }, + "payload": { + "$ref": "#/definitions/DeviceKeySignPayload" + } + }, + "required": [ + "keyId", + "payload" + ], + "title": "DeviceKeySignParams", + "type": "object" + }, + "DeviceKeySignPayload": { + "description": "Structured payloads accepted by `device/key/sign`.", + "oneOf": [ + { + "description": "Payload bound to one remote-control controller websocket `/client` connection challenge.", + "properties": { + "accountUserId": { + "type": "string" + }, + "audience": { + "$ref": "#/definitions/RemoteControlClientConnectionAudience" + }, + "clientId": { + "type": "string" + }, + "nonce": { + "type": "string" + }, + "scopes": { + "description": "Must contain exactly `remote_control_controller_websocket`.", + "items": { + "type": "string" + }, + "type": "array" + }, + "sessionId": { + "description": "Backend-issued websocket session id that this proof authorizes.", + "type": "string" + }, + "targetOrigin": { + "description": "Origin of the backend endpoint that issued the challenge and will verify this proof.", + "type": "string" + }, + "targetPath": { + "description": "Websocket route path that this proof authorizes.", + "type": "string" + }, + "tokenExpiresAt": { + "description": "Remote-control token expiration as Unix seconds.", + "format": "int64", + "type": "integer" + }, + "tokenSha256Base64url": { + "description": "SHA-256 of the controller-scoped remote-control token, encoded as unpadded base64url.", + "type": "string" + }, + "type": { + "enum": [ + "remoteControlClientConnection" + ], + "title": "RemoteControlClientConnectionDeviceKeySignPayloadType", + "type": "string" + } + }, + "required": [ + "accountUserId", + "audience", + "clientId", + "nonce", + "scopes", + "sessionId", + "targetOrigin", + "targetPath", + "tokenExpiresAt", + "tokenSha256Base64url", + "type" + ], + "title": "RemoteControlClientConnectionDeviceKeySignPayload", + "type": "object" + }, + { + "description": "Payload bound to a remote-control client `/client/enroll` ownership challenge.", + "properties": { + "accountUserId": { + "type": "string" + }, + "audience": { + "$ref": "#/definitions/RemoteControlClientEnrollmentAudience" + }, + "challengeExpiresAt": { + "description": "Enrollment challenge expiration as Unix seconds.", + "format": "int64", + "type": "integer" + }, + "challengeId": { + "description": "Backend-issued enrollment challenge id that this proof authorizes.", + "type": "string" + }, + "clientId": { + "type": "string" + }, + "deviceIdentitySha256Base64url": { + "description": "SHA-256 of the requested device identity operation, encoded as unpadded base64url.", + "type": "string" + }, + "nonce": { + "type": "string" + }, + "targetOrigin": { + "description": "Origin of the backend endpoint that issued the challenge and will verify this proof.", + "type": "string" + }, + "targetPath": { + "description": "HTTP route path that this proof authorizes.", + "type": "string" + }, + "type": { + "enum": [ + "remoteControlClientEnrollment" + ], + "title": "RemoteControlClientEnrollmentDeviceKeySignPayloadType", + "type": "string" + } + }, + "required": [ + "accountUserId", + "audience", + "challengeExpiresAt", + "challengeId", + "clientId", + "deviceIdentitySha256Base64url", + "nonce", + "targetOrigin", + "targetPath", + "type" + ], + "title": "RemoteControlClientEnrollmentDeviceKeySignPayload", + "type": "object" + } + ] + }, + "DeviceKeySignResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "ASN.1 DER signature returned by `device/key/sign`.", + "properties": { + "algorithm": { + "$ref": "#/definitions/DeviceKeyAlgorithm" + }, + "signatureDerBase64": { + "description": "ECDSA signature DER encoded as base64.", + "type": "string" + }, + "signedPayloadBase64": { + "description": "Exact bytes signed by the device key, encoded as base64. Verifiers must verify this byte string directly and must not reserialize `payload`.", + "type": "string" + } + }, + "required": [ + "algorithm", + "signatureDerBase64", + "signedPayloadBase64" + ], + "title": "DeviceKeySignResponse", + "type": "object" + }, "DynamicToolCallOutputContentItem": { "oneOf": [ { @@ -8408,6 +8774,20 @@ "title": "ReasoningTextDeltaNotification", "type": "object" }, + "RemoteControlClientConnectionAudience": { + "description": "Audience for a remote-control client connection device-key proof.", + "enum": [ + "remote_control_client_websocket" + ], + "type": "string" + }, + "RemoteControlClientEnrollmentAudience": { + "description": "Audience for a remote-control client enrollment device-key proof.", + "enum": [ + "remote_control_client_enrollment" + ], + "type": "string" + }, "RequestId": { "anyOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/DeviceKeyCreateParams.json b/codex-rs/app-server-protocol/schema/json/v2/DeviceKeyCreateParams.json new file mode 100644 index 000000000..fe2c0f089 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/DeviceKeyCreateParams.json @@ -0,0 +1,39 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "DeviceKeyProtectionPolicy": { + "description": "Protection policy for creating or loading a controller-local device key.", + "enum": [ + "hardware_only", + "allow_os_protected_nonextractable" + ], + "type": "string" + } + }, + "description": "Create a controller-local device key with a random key id.", + "properties": { + "accountUserId": { + "type": "string" + }, + "clientId": { + "type": "string" + }, + "protectionPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/DeviceKeyProtectionPolicy" + }, + { + "type": "null" + } + ], + "description": "Defaults to `hardware_only` when omitted." + } + }, + "required": [ + "accountUserId", + "clientId" + ], + "title": "DeviceKeyCreateParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/DeviceKeyCreateResponse.json b/codex-rs/app-server-protocol/schema/json/v2/DeviceKeyCreateResponse.json new file mode 100644 index 000000000..12072588a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/DeviceKeyCreateResponse.json @@ -0,0 +1,45 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "DeviceKeyAlgorithm": { + "description": "Device-key algorithm reported at enrollment and signing boundaries.", + "enum": [ + "ecdsa_p256_sha256" + ], + "type": "string" + }, + "DeviceKeyProtectionClass": { + "description": "Platform protection class for a controller-local device key.", + "enum": [ + "hardware_secure_enclave", + "hardware_tpm", + "os_protected_nonextractable" + ], + "type": "string" + } + }, + "description": "Device-key metadata and public key returned by create/public APIs.", + "properties": { + "algorithm": { + "$ref": "#/definitions/DeviceKeyAlgorithm" + }, + "keyId": { + "type": "string" + }, + "protectionClass": { + "$ref": "#/definitions/DeviceKeyProtectionClass" + }, + "publicKeySpkiDerBase64": { + "description": "SubjectPublicKeyInfo DER encoded as base64.", + "type": "string" + } + }, + "required": [ + "algorithm", + "keyId", + "protectionClass", + "publicKeySpkiDerBase64" + ], + "title": "DeviceKeyCreateResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/DeviceKeyPublicParams.json b/codex-rs/app-server-protocol/schema/json/v2/DeviceKeyPublicParams.json new file mode 100644 index 000000000..37cc5fbe2 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/DeviceKeyPublicParams.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Fetch a controller-local device key public key by id.", + "properties": { + "keyId": { + "type": "string" + } + }, + "required": [ + "keyId" + ], + "title": "DeviceKeyPublicParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/DeviceKeyPublicResponse.json b/codex-rs/app-server-protocol/schema/json/v2/DeviceKeyPublicResponse.json new file mode 100644 index 000000000..39f98b762 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/DeviceKeyPublicResponse.json @@ -0,0 +1,45 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "DeviceKeyAlgorithm": { + "description": "Device-key algorithm reported at enrollment and signing boundaries.", + "enum": [ + "ecdsa_p256_sha256" + ], + "type": "string" + }, + "DeviceKeyProtectionClass": { + "description": "Platform protection class for a controller-local device key.", + "enum": [ + "hardware_secure_enclave", + "hardware_tpm", + "os_protected_nonextractable" + ], + "type": "string" + } + }, + "description": "Device-key public metadata returned by `device/key/public`.", + "properties": { + "algorithm": { + "$ref": "#/definitions/DeviceKeyAlgorithm" + }, + "keyId": { + "type": "string" + }, + "protectionClass": { + "$ref": "#/definitions/DeviceKeyProtectionClass" + }, + "publicKeySpkiDerBase64": { + "description": "SubjectPublicKeyInfo DER encoded as base64.", + "type": "string" + } + }, + "required": [ + "algorithm", + "keyId", + "protectionClass", + "publicKeySpkiDerBase64" + ], + "title": "DeviceKeyPublicResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/DeviceKeySignParams.json b/codex-rs/app-server-protocol/schema/json/v2/DeviceKeySignParams.json new file mode 100644 index 000000000..054765b29 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/DeviceKeySignParams.json @@ -0,0 +1,165 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "DeviceKeySignPayload": { + "description": "Structured payloads accepted by `device/key/sign`.", + "oneOf": [ + { + "description": "Payload bound to one remote-control controller websocket `/client` connection challenge.", + "properties": { + "accountUserId": { + "type": "string" + }, + "audience": { + "$ref": "#/definitions/RemoteControlClientConnectionAudience" + }, + "clientId": { + "type": "string" + }, + "nonce": { + "type": "string" + }, + "scopes": { + "description": "Must contain exactly `remote_control_controller_websocket`.", + "items": { + "type": "string" + }, + "type": "array" + }, + "sessionId": { + "description": "Backend-issued websocket session id that this proof authorizes.", + "type": "string" + }, + "targetOrigin": { + "description": "Origin of the backend endpoint that issued the challenge and will verify this proof.", + "type": "string" + }, + "targetPath": { + "description": "Websocket route path that this proof authorizes.", + "type": "string" + }, + "tokenExpiresAt": { + "description": "Remote-control token expiration as Unix seconds.", + "format": "int64", + "type": "integer" + }, + "tokenSha256Base64url": { + "description": "SHA-256 of the controller-scoped remote-control token, encoded as unpadded base64url.", + "type": "string" + }, + "type": { + "enum": [ + "remoteControlClientConnection" + ], + "title": "RemoteControlClientConnectionDeviceKeySignPayloadType", + "type": "string" + } + }, + "required": [ + "accountUserId", + "audience", + "clientId", + "nonce", + "scopes", + "sessionId", + "targetOrigin", + "targetPath", + "tokenExpiresAt", + "tokenSha256Base64url", + "type" + ], + "title": "RemoteControlClientConnectionDeviceKeySignPayload", + "type": "object" + }, + { + "description": "Payload bound to a remote-control client `/client/enroll` ownership challenge.", + "properties": { + "accountUserId": { + "type": "string" + }, + "audience": { + "$ref": "#/definitions/RemoteControlClientEnrollmentAudience" + }, + "challengeExpiresAt": { + "description": "Enrollment challenge expiration as Unix seconds.", + "format": "int64", + "type": "integer" + }, + "challengeId": { + "description": "Backend-issued enrollment challenge id that this proof authorizes.", + "type": "string" + }, + "clientId": { + "type": "string" + }, + "deviceIdentitySha256Base64url": { + "description": "SHA-256 of the requested device identity operation, encoded as unpadded base64url.", + "type": "string" + }, + "nonce": { + "type": "string" + }, + "targetOrigin": { + "description": "Origin of the backend endpoint that issued the challenge and will verify this proof.", + "type": "string" + }, + "targetPath": { + "description": "HTTP route path that this proof authorizes.", + "type": "string" + }, + "type": { + "enum": [ + "remoteControlClientEnrollment" + ], + "title": "RemoteControlClientEnrollmentDeviceKeySignPayloadType", + "type": "string" + } + }, + "required": [ + "accountUserId", + "audience", + "challengeExpiresAt", + "challengeId", + "clientId", + "deviceIdentitySha256Base64url", + "nonce", + "targetOrigin", + "targetPath", + "type" + ], + "title": "RemoteControlClientEnrollmentDeviceKeySignPayload", + "type": "object" + } + ] + }, + "RemoteControlClientConnectionAudience": { + "description": "Audience for a remote-control client connection device-key proof.", + "enum": [ + "remote_control_client_websocket" + ], + "type": "string" + }, + "RemoteControlClientEnrollmentAudience": { + "description": "Audience for a remote-control client enrollment device-key proof.", + "enum": [ + "remote_control_client_enrollment" + ], + "type": "string" + } + }, + "description": "Sign an accepted structured payload with a controller-local device key.", + "properties": { + "keyId": { + "type": "string" + }, + "payload": { + "$ref": "#/definitions/DeviceKeySignPayload" + } + }, + "required": [ + "keyId", + "payload" + ], + "title": "DeviceKeySignParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/DeviceKeySignResponse.json b/codex-rs/app-server-protocol/schema/json/v2/DeviceKeySignResponse.json new file mode 100644 index 000000000..83fec9033 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/DeviceKeySignResponse.json @@ -0,0 +1,33 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "DeviceKeyAlgorithm": { + "description": "Device-key algorithm reported at enrollment and signing boundaries.", + "enum": [ + "ecdsa_p256_sha256" + ], + "type": "string" + } + }, + "description": "ASN.1 DER signature returned by `device/key/sign`.", + "properties": { + "algorithm": { + "$ref": "#/definitions/DeviceKeyAlgorithm" + }, + "signatureDerBase64": { + "description": "ECDSA signature DER encoded as base64.", + "type": "string" + }, + "signedPayloadBase64": { + "description": "Exact bytes signed by the device key, encoded as base64. Verifiers must verify this byte string directly and must not reserialize `payload`.", + "type": "string" + } + }, + "required": [ + "algorithm", + "signatureDerBase64", + "signedPayloadBase64" + ], + "title": "DeviceKeySignResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts index c26ff9161..fdb934ed5 100644 --- a/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts +++ b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts @@ -16,6 +16,9 @@ import type { CommandExecWriteParams } from "./v2/CommandExecWriteParams"; import type { ConfigBatchWriteParams } from "./v2/ConfigBatchWriteParams"; import type { ConfigReadParams } from "./v2/ConfigReadParams"; import type { ConfigValueWriteParams } from "./v2/ConfigValueWriteParams"; +import type { DeviceKeyCreateParams } from "./v2/DeviceKeyCreateParams"; +import type { DeviceKeyPublicParams } from "./v2/DeviceKeyPublicParams"; +import type { DeviceKeySignParams } from "./v2/DeviceKeySignParams"; import type { ExperimentalFeatureEnablementSetParams } from "./v2/ExperimentalFeatureEnablementSetParams"; import type { ExperimentalFeatureListParams } from "./v2/ExperimentalFeatureListParams"; import type { ExternalAgentConfigDetectParams } from "./v2/ExternalAgentConfigDetectParams"; @@ -71,4 +74,4 @@ import type { WindowsSandboxSetupStartParams } from "./v2/WindowsSandboxSetupSta /** * Request from the client to the server. */ -export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/unsubscribe", id: RequestId, params: ThreadUnsubscribeParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/metadata/update", id: RequestId, params: ThreadMetadataUpdateParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/compact/start", id: RequestId, params: ThreadCompactStartParams, } | { "method": "thread/shellCommand", id: RequestId, params: ThreadShellCommandParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "thread/turns/list", id: RequestId, params: ThreadTurnsListParams, } | { "method": "thread/inject_items", id: RequestId, params: ThreadInjectItemsParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "marketplace/add", id: RequestId, params: MarketplaceAddParams, } | { "method": "marketplace/remove", id: RequestId, params: MarketplaceRemoveParams, } | { "method": "plugin/list", id: RequestId, params: PluginListParams, } | { "method": "plugin/read", id: RequestId, params: PluginReadParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "fs/readFile", id: RequestId, params: FsReadFileParams, } | { "method": "fs/writeFile", id: RequestId, params: FsWriteFileParams, } | { "method": "fs/createDirectory", id: RequestId, params: FsCreateDirectoryParams, } | { "method": "fs/getMetadata", id: RequestId, params: FsGetMetadataParams, } | { "method": "fs/readDirectory", id: RequestId, params: FsReadDirectoryParams, } | { "method": "fs/remove", id: RequestId, params: FsRemoveParams, } | { "method": "fs/copy", id: RequestId, params: FsCopyParams, } | { "method": "fs/watch", id: RequestId, params: FsWatchParams, } | { "method": "fs/unwatch", id: RequestId, params: FsUnwatchParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "plugin/install", id: RequestId, params: PluginInstallParams, } | { "method": "plugin/uninstall", id: RequestId, params: PluginUninstallParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/steer", id: RequestId, params: TurnSteerParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "experimentalFeature/list", id: RequestId, params: ExperimentalFeatureListParams, } | { "method": "experimentalFeature/enablement/set", id: RequestId, params: ExperimentalFeatureEnablementSetParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "mcpServer/resource/read", id: RequestId, params: McpResourceReadParams, } | { "method": "mcpServer/tool/call", id: RequestId, params: McpServerToolCallParams, } | { "method": "windowsSandbox/setupStart", id: RequestId, params: WindowsSandboxSetupStartParams, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "account/sendAddCreditsNudgeEmail", id: RequestId, params: SendAddCreditsNudgeEmailParams, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "command/exec/write", id: RequestId, params: CommandExecWriteParams, } | { "method": "command/exec/terminate", id: RequestId, params: CommandExecTerminateParams, } | { "method": "command/exec/resize", id: RequestId, params: CommandExecResizeParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "externalAgentConfig/detect", id: RequestId, params: ExternalAgentConfigDetectParams, } | { "method": "externalAgentConfig/import", id: RequestId, params: ExternalAgentConfigImportParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, }; +export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/unsubscribe", id: RequestId, params: ThreadUnsubscribeParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/metadata/update", id: RequestId, params: ThreadMetadataUpdateParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/compact/start", id: RequestId, params: ThreadCompactStartParams, } | { "method": "thread/shellCommand", id: RequestId, params: ThreadShellCommandParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "thread/turns/list", id: RequestId, params: ThreadTurnsListParams, } | { "method": "thread/inject_items", id: RequestId, params: ThreadInjectItemsParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "marketplace/add", id: RequestId, params: MarketplaceAddParams, } | { "method": "marketplace/remove", id: RequestId, params: MarketplaceRemoveParams, } | { "method": "plugin/list", id: RequestId, params: PluginListParams, } | { "method": "plugin/read", id: RequestId, params: PluginReadParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "device/key/create", id: RequestId, params: DeviceKeyCreateParams, } | { "method": "device/key/public", id: RequestId, params: DeviceKeyPublicParams, } | { "method": "device/key/sign", id: RequestId, params: DeviceKeySignParams, } | { "method": "fs/readFile", id: RequestId, params: FsReadFileParams, } | { "method": "fs/writeFile", id: RequestId, params: FsWriteFileParams, } | { "method": "fs/createDirectory", id: RequestId, params: FsCreateDirectoryParams, } | { "method": "fs/getMetadata", id: RequestId, params: FsGetMetadataParams, } | { "method": "fs/readDirectory", id: RequestId, params: FsReadDirectoryParams, } | { "method": "fs/remove", id: RequestId, params: FsRemoveParams, } | { "method": "fs/copy", id: RequestId, params: FsCopyParams, } | { "method": "fs/watch", id: RequestId, params: FsWatchParams, } | { "method": "fs/unwatch", id: RequestId, params: FsUnwatchParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "plugin/install", id: RequestId, params: PluginInstallParams, } | { "method": "plugin/uninstall", id: RequestId, params: PluginUninstallParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/steer", id: RequestId, params: TurnSteerParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "experimentalFeature/list", id: RequestId, params: ExperimentalFeatureListParams, } | { "method": "experimentalFeature/enablement/set", id: RequestId, params: ExperimentalFeatureEnablementSetParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "mcpServer/resource/read", id: RequestId, params: McpResourceReadParams, } | { "method": "mcpServer/tool/call", id: RequestId, params: McpServerToolCallParams, } | { "method": "windowsSandbox/setupStart", id: RequestId, params: WindowsSandboxSetupStartParams, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "account/sendAddCreditsNudgeEmail", id: RequestId, params: SendAddCreditsNudgeEmailParams, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "command/exec/write", id: RequestId, params: CommandExecWriteParams, } | { "method": "command/exec/terminate", id: RequestId, params: CommandExecTerminateParams, } | { "method": "command/exec/resize", id: RequestId, params: CommandExecResizeParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "externalAgentConfig/detect", id: RequestId, params: ExternalAgentConfigDetectParams, } | { "method": "externalAgentConfig/import", id: RequestId, params: ExternalAgentConfigImportParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyAlgorithm.ts b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyAlgorithm.ts new file mode 100644 index 000000000..6809c41eb --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyAlgorithm.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Device-key algorithm reported at enrollment and signing boundaries. + */ +export type DeviceKeyAlgorithm = "ecdsa_p256_sha256"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyCreateParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyCreateParams.ts new file mode 100644 index 000000000..7ffd9b5fa --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyCreateParams.ts @@ -0,0 +1,13 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DeviceKeyProtectionPolicy } from "./DeviceKeyProtectionPolicy"; + +/** + * Create a controller-local device key with a random key id. + */ +export type DeviceKeyCreateParams = { +/** + * Defaults to `hardware_only` when omitted. + */ +protectionPolicy?: DeviceKeyProtectionPolicy | null, accountUserId: string, clientId: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyCreateResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyCreateResponse.ts new file mode 100644 index 000000000..6ace37934 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyCreateResponse.ts @@ -0,0 +1,14 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DeviceKeyAlgorithm } from "./DeviceKeyAlgorithm"; +import type { DeviceKeyProtectionClass } from "./DeviceKeyProtectionClass"; + +/** + * Device-key metadata and public key returned by create/public APIs. + */ +export type DeviceKeyCreateResponse = { keyId: string, +/** + * SubjectPublicKeyInfo DER encoded as base64. + */ +publicKeySpkiDerBase64: string, algorithm: DeviceKeyAlgorithm, protectionClass: DeviceKeyProtectionClass, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyProtectionClass.ts b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyProtectionClass.ts new file mode 100644 index 000000000..ba7ff311a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyProtectionClass.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Platform protection class for a controller-local device key. + */ +export type DeviceKeyProtectionClass = "hardware_secure_enclave" | "hardware_tpm" | "os_protected_nonextractable"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyProtectionPolicy.ts b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyProtectionPolicy.ts new file mode 100644 index 000000000..66fceafb5 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyProtectionPolicy.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Protection policy for creating or loading a controller-local device key. + */ +export type DeviceKeyProtectionPolicy = "hardware_only" | "allow_os_protected_nonextractable"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyPublicParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyPublicParams.ts new file mode 100644 index 000000000..5a5b77899 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyPublicParams.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Fetch a controller-local device key public key by id. + */ +export type DeviceKeyPublicParams = { keyId: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyPublicResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyPublicResponse.ts new file mode 100644 index 000000000..9967c0936 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyPublicResponse.ts @@ -0,0 +1,14 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DeviceKeyAlgorithm } from "./DeviceKeyAlgorithm"; +import type { DeviceKeyProtectionClass } from "./DeviceKeyProtectionClass"; + +/** + * Device-key public metadata returned by `device/key/public`. + */ +export type DeviceKeyPublicResponse = { keyId: string, +/** + * SubjectPublicKeyInfo DER encoded as base64. + */ +publicKeySpkiDerBase64: string, algorithm: DeviceKeyAlgorithm, protectionClass: DeviceKeyProtectionClass, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeySignParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeySignParams.ts new file mode 100644 index 000000000..0886e45d9 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeySignParams.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DeviceKeySignPayload } from "./DeviceKeySignPayload"; + +/** + * Sign an accepted structured payload with a controller-local device key. + */ +export type DeviceKeySignParams = { keyId: string, payload: DeviceKeySignPayload, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeySignPayload.ts b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeySignPayload.ts new file mode 100644 index 000000000..859644549 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeySignPayload.ts @@ -0,0 +1,54 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { RemoteControlClientConnectionAudience } from "./RemoteControlClientConnectionAudience"; +import type { RemoteControlClientEnrollmentAudience } from "./RemoteControlClientEnrollmentAudience"; + +/** + * Structured payloads accepted by `device/key/sign`. + */ +export type DeviceKeySignPayload = { "type": "remoteControlClientConnection", nonce: string, audience: RemoteControlClientConnectionAudience, +/** + * Backend-issued websocket session id that this proof authorizes. + */ +sessionId: string, +/** + * Origin of the backend endpoint that issued the challenge and will verify this proof. + */ +targetOrigin: string, +/** + * Websocket route path that this proof authorizes. + */ +targetPath: string, accountUserId: string, clientId: string, +/** + * Remote-control token expiration as Unix seconds. + */ +tokenExpiresAt: number, +/** + * SHA-256 of the controller-scoped remote-control token, encoded as unpadded base64url. + */ +tokenSha256Base64url: string, +/** + * Must contain exactly `remote_control_controller_websocket`. + */ +scopes: Array, } | { "type": "remoteControlClientEnrollment", nonce: string, audience: RemoteControlClientEnrollmentAudience, +/** + * Backend-issued enrollment challenge id that this proof authorizes. + */ +challengeId: string, +/** + * Origin of the backend endpoint that issued the challenge and will verify this proof. + */ +targetOrigin: string, +/** + * HTTP route path that this proof authorizes. + */ +targetPath: string, accountUserId: string, clientId: string, +/** + * SHA-256 of the requested device identity operation, encoded as unpadded base64url. + */ +deviceIdentitySha256Base64url: string, +/** + * Enrollment challenge expiration as Unix seconds. + */ +challengeExpiresAt: number, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeySignResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeySignResponse.ts new file mode 100644 index 000000000..cf77fae27 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeySignResponse.ts @@ -0,0 +1,18 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DeviceKeyAlgorithm } from "./DeviceKeyAlgorithm"; + +/** + * ASN.1 DER signature returned by `device/key/sign`. + */ +export type DeviceKeySignResponse = { +/** + * ECDSA signature DER encoded as base64. + */ +signatureDerBase64: string, +/** + * Exact bytes signed by the device key, encoded as base64. Verifiers must verify this byte + * string directly and must not reserialize `payload`. + */ +signedPayloadBase64: string, algorithm: DeviceKeyAlgorithm, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/RemoteControlClientConnectionAudience.ts b/codex-rs/app-server-protocol/schema/typescript/v2/RemoteControlClientConnectionAudience.ts new file mode 100644 index 000000000..e4d41ff4c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/RemoteControlClientConnectionAudience.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Audience for a remote-control client connection device-key proof. + */ +export type RemoteControlClientConnectionAudience = "remote_control_client_websocket"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/RemoteControlClientEnrollmentAudience.ts b/codex-rs/app-server-protocol/schema/typescript/v2/RemoteControlClientEnrollmentAudience.ts new file mode 100644 index 000000000..b65fb3d11 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/RemoteControlClientEnrollmentAudience.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Audience for a remote-control client enrollment device-key proof. + */ +export type RemoteControlClientEnrollmentAudience = "remote_control_client_enrollment"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index e37759112..7bddd0f9d 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -74,6 +74,16 @@ export type { ConfigWriteResponse } from "./ConfigWriteResponse"; export type { ContextCompactedNotification } from "./ContextCompactedNotification"; export type { CreditsSnapshot } from "./CreditsSnapshot"; export type { DeprecationNoticeNotification } from "./DeprecationNoticeNotification"; +export type { DeviceKeyAlgorithm } from "./DeviceKeyAlgorithm"; +export type { DeviceKeyCreateParams } from "./DeviceKeyCreateParams"; +export type { DeviceKeyCreateResponse } from "./DeviceKeyCreateResponse"; +export type { DeviceKeyProtectionClass } from "./DeviceKeyProtectionClass"; +export type { DeviceKeyProtectionPolicy } from "./DeviceKeyProtectionPolicy"; +export type { DeviceKeyPublicParams } from "./DeviceKeyPublicParams"; +export type { DeviceKeyPublicResponse } from "./DeviceKeyPublicResponse"; +export type { DeviceKeySignParams } from "./DeviceKeySignParams"; +export type { DeviceKeySignPayload } from "./DeviceKeySignPayload"; +export type { DeviceKeySignResponse } from "./DeviceKeySignResponse"; export type { DynamicToolCallOutputContentItem } from "./DynamicToolCallOutputContentItem"; export type { DynamicToolCallParams } from "./DynamicToolCallParams"; export type { DynamicToolCallResponse } from "./DynamicToolCallResponse"; @@ -259,6 +269,8 @@ export type { ReasoningEffortOption } from "./ReasoningEffortOption"; export type { ReasoningSummaryPartAddedNotification } from "./ReasoningSummaryPartAddedNotification"; export type { ReasoningSummaryTextDeltaNotification } from "./ReasoningSummaryTextDeltaNotification"; export type { ReasoningTextDeltaNotification } from "./ReasoningTextDeltaNotification"; +export type { RemoteControlClientConnectionAudience } from "./RemoteControlClientConnectionAudience"; +export type { RemoteControlClientEnrollmentAudience } from "./RemoteControlClientEnrollmentAudience"; export type { RequestPermissionProfile } from "./RequestPermissionProfile"; export type { ResidencyRequirement } from "./ResidencyRequirement"; export type { ReviewDelivery } from "./ReviewDelivery"; diff --git a/codex-rs/app-server-protocol/src/protocol/common.rs b/codex-rs/app-server-protocol/src/protocol/common.rs index aa3ff8331..4179e18b5 100644 --- a/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/codex-rs/app-server-protocol/src/protocol/common.rs @@ -360,6 +360,18 @@ client_request_definitions! { params: v2::AppsListParams, response: v2::AppsListResponse, }, + DeviceKeyCreate => "device/key/create" { + params: v2::DeviceKeyCreateParams, + response: v2::DeviceKeyCreateResponse, + }, + DeviceKeyPublic => "device/key/public" { + params: v2::DeviceKeyPublicParams, + response: v2::DeviceKeyPublicResponse, + }, + DeviceKeySign => "device/key/sign" { + params: v2::DeviceKeySignParams, + response: v2::DeviceKeySignResponse, + }, FsReadFile => "fs/readFile" { params: v2::FsReadFileParams, response: v2::FsReadFileResponse, diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 8ac6651ae..a78db2e0e 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -2494,6 +2494,164 @@ pub struct FeedbackUploadResponse { pub thread_id: String, } +/// Device-key algorithm reported at enrollment and signing boundaries. +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "snake_case")] +#[ts(rename_all = "snake_case", export_to = "v2/")] +pub enum DeviceKeyAlgorithm { + EcdsaP256Sha256, +} + +/// Platform protection class for a controller-local device key. +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "snake_case")] +#[ts(rename_all = "snake_case", export_to = "v2/")] +pub enum DeviceKeyProtectionClass { + HardwareSecureEnclave, + HardwareTpm, + OsProtectedNonextractable, +} + +/// Protection policy for creating or loading a controller-local device key. +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "snake_case")] +#[ts(rename_all = "snake_case", export_to = "v2/")] +pub enum DeviceKeyProtectionPolicy { + HardwareOnly, + AllowOsProtectedNonextractable, +} + +/// Create a controller-local device key with a random key id. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct DeviceKeyCreateParams { + /// Defaults to `hardware_only` when omitted. + #[ts(optional = nullable)] + pub protection_policy: Option, + pub account_user_id: String, + pub client_id: String, +} + +/// Device-key metadata and public key returned by create/public APIs. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct DeviceKeyCreateResponse { + pub key_id: String, + /// SubjectPublicKeyInfo DER encoded as base64. + pub public_key_spki_der_base64: String, + pub algorithm: DeviceKeyAlgorithm, + pub protection_class: DeviceKeyProtectionClass, +} + +/// Fetch a controller-local device key public key by id. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct DeviceKeyPublicParams { + pub key_id: String, +} + +/// Device-key public metadata returned by `device/key/public`. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct DeviceKeyPublicResponse { + pub key_id: String, + /// SubjectPublicKeyInfo DER encoded as base64. + pub public_key_spki_der_base64: String, + pub algorithm: DeviceKeyAlgorithm, + pub protection_class: DeviceKeyProtectionClass, +} + +/// Audience for a remote-control client connection device-key proof. +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "snake_case")] +#[ts(rename_all = "snake_case", export_to = "v2/")] +pub enum RemoteControlClientConnectionAudience { + RemoteControlClientWebsocket, +} + +/// Audience for a remote-control client enrollment device-key proof. +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "snake_case")] +#[ts(rename_all = "snake_case", export_to = "v2/")] +pub enum RemoteControlClientEnrollmentAudience { + RemoteControlClientEnrollment, +} + +/// Structured payloads accepted by `device/key/sign`. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(tag = "type", rename_all = "camelCase")] +#[ts(tag = "type", export_to = "v2/")] +pub enum DeviceKeySignPayload { + /// Payload bound to one remote-control controller websocket `/client` connection challenge. + #[serde(rename_all = "camelCase")] + #[ts(rename_all = "camelCase")] + RemoteControlClientConnection { + nonce: String, + audience: RemoteControlClientConnectionAudience, + /// Backend-issued websocket session id that this proof authorizes. + session_id: String, + /// Origin of the backend endpoint that issued the challenge and will verify this proof. + target_origin: String, + /// Websocket route path that this proof authorizes. + target_path: String, + account_user_id: String, + client_id: String, + /// Remote-control token expiration as Unix seconds. + #[ts(type = "number")] + token_expires_at: i64, + /// SHA-256 of the controller-scoped remote-control token, encoded as unpadded base64url. + token_sha256_base64url: String, + /// Must contain exactly `remote_control_controller_websocket`. + scopes: Vec, + }, + /// Payload bound to a remote-control client `/client/enroll` ownership challenge. + #[serde(rename_all = "camelCase")] + #[ts(rename_all = "camelCase")] + RemoteControlClientEnrollment { + nonce: String, + audience: RemoteControlClientEnrollmentAudience, + /// Backend-issued enrollment challenge id that this proof authorizes. + challenge_id: String, + /// Origin of the backend endpoint that issued the challenge and will verify this proof. + target_origin: String, + /// HTTP route path that this proof authorizes. + target_path: String, + account_user_id: String, + client_id: String, + /// SHA-256 of the requested device identity operation, encoded as unpadded base64url. + device_identity_sha256_base64url: String, + /// Enrollment challenge expiration as Unix seconds. + #[ts(type = "number")] + challenge_expires_at: i64, + }, +} + +/// Sign an accepted structured payload with a controller-local device key. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct DeviceKeySignParams { + pub key_id: String, + pub payload: DeviceKeySignPayload, +} + +/// ASN.1 DER signature returned by `device/key/sign`. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct DeviceKeySignResponse { + /// ECDSA signature DER encoded as base64. + pub signature_der_base64: String, + /// Exact bytes signed by the device key, encoded as base64. Verifiers must verify this byte + /// string directly and must not reserialize `payload`. + pub signed_payload_base64: String, + pub algorithm: DeviceKeyAlgorithm, +} + /// Read a file from the host filesystem. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] @@ -7386,6 +7544,181 @@ mod tests { assert_eq!(decoded, params); } + #[test] + fn device_key_create_params_round_trip_uses_protection_policy() { + let params = DeviceKeyCreateParams { + protection_policy: None, + account_user_id: "account-user-1".to_string(), + client_id: "cli_123".to_string(), + }; + + let value = serde_json::to_value(¶ms).expect("serialize device/key/create params"); + assert_eq!( + value, + json!({ + "accountUserId": "account-user-1", + "clientId": "cli_123", + "protectionPolicy": null, + }) + ); + + let decoded = serde_json::from_value::(value) + .expect("deserialize device/key/create params"); + assert_eq!(decoded, params); + + let params = DeviceKeyCreateParams { + protection_policy: Some(DeviceKeyProtectionPolicy::AllowOsProtectedNonextractable), + account_user_id: "account-user-1".to_string(), + client_id: "cli_123".to_string(), + }; + let value = serde_json::to_value(¶ms) + .expect("serialize device/key/create params with protection policy"); + assert_eq!( + value, + json!({ + "accountUserId": "account-user-1", + "clientId": "cli_123", + "protectionPolicy": "allow_os_protected_nonextractable", + }) + ); + } + + #[test] + fn device_key_create_response_round_trips_protection_class() { + let response = DeviceKeyCreateResponse { + key_id: "dk_123".to_string(), + public_key_spki_der_base64: "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE".to_string(), + algorithm: DeviceKeyAlgorithm::EcdsaP256Sha256, + protection_class: DeviceKeyProtectionClass::OsProtectedNonextractable, + }; + + let value = serde_json::to_value(&response).expect("serialize device/key/create response"); + assert_eq!( + value, + json!({ + "keyId": "dk_123", + "publicKeySpkiDerBase64": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE", + "algorithm": "ecdsa_p256_sha256", + "protectionClass": "os_protected_nonextractable", + }) + ); + + let decoded = serde_json::from_value::(value) + .expect("deserialize device/key/create response"); + assert_eq!(decoded, response); + } + + #[test] + fn device_key_sign_params_round_trip_uses_accepted_payload_enum() { + let params = DeviceKeySignParams { + key_id: "dk_123".to_string(), + payload: DeviceKeySignPayload::RemoteControlClientConnection { + nonce: "nonce-1".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: "account-user-1".to_string(), + client_id: "cli_123".to_string(), + token_sha256_base64url: "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU".to_string(), + token_expires_at: 1_700_000_000, + scopes: vec!["remote_control_controller_websocket".to_string()], + }, + }; + + let value = serde_json::to_value(¶ms).expect("serialize device/key/sign params"); + assert_eq!( + value, + json!({ + "keyId": "dk_123", + "payload": { + "type": "remoteControlClientConnection", + "nonce": "nonce-1", + "audience": "remote_control_client_websocket", + "sessionId": "wssess_123", + "targetOrigin": "https://chatgpt.com", + "targetPath": "/api/codex/remote/control/client", + "accountUserId": "account-user-1", + "clientId": "cli_123", + "tokenSha256Base64url": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU", + "tokenExpiresAt": 1_700_000_000, + "scopes": ["remote_control_controller_websocket"], + }, + }) + ); + + let decoded = serde_json::from_value::(value) + .expect("deserialize device/key/sign params"); + assert_eq!(decoded, params); + } + + #[test] + fn device_key_sign_params_round_trip_uses_enrollment_payload() { + let params = DeviceKeySignParams { + key_id: "dk_123".to_string(), + payload: DeviceKeySignPayload::RemoteControlClientEnrollment { + nonce: "nonce-1".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: "account-user-1".to_string(), + client_id: "cli_123".to_string(), + device_identity_sha256_base64url: "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" + .to_string(), + challenge_expires_at: 1_700_000_000, + }, + }; + + let value = serde_json::to_value(¶ms) + .expect("serialize device/key/sign params with enrollment payload"); + assert_eq!( + value, + json!({ + "keyId": "dk_123", + "payload": { + "type": "remoteControlClientEnrollment", + "nonce": "nonce-1", + "audience": "remote_control_client_enrollment", + "challengeId": "rch_123", + "targetOrigin": "https://chatgpt.com", + "targetPath": "/wham/remote/control/client/enroll", + "accountUserId": "account-user-1", + "clientId": "cli_123", + "deviceIdentitySha256Base64url": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU", + "challengeExpiresAt": 1_700_000_000, + }, + }) + ); + + let decoded = serde_json::from_value::(value) + .expect("deserialize device/key/sign params with enrollment payload"); + assert_eq!(decoded, params); + } + + #[test] + fn device_key_sign_response_returns_signed_payload_bytes() { + let response = DeviceKeySignResponse { + signature_der_base64: "MEUCIQD".to_string(), + signed_payload_base64: "eyJkb21haW4iOiJjb2RleA".to_string(), + algorithm: DeviceKeyAlgorithm::EcdsaP256Sha256, + }; + + let value = serde_json::to_value(&response).expect("serialize device/key/sign response"); + assert_eq!( + value, + json!({ + "signatureDerBase64": "MEUCIQD", + "signedPayloadBase64": "eyJkb21haW4iOiJjb2RleA", + "algorithm": "ecdsa_p256_sha256", + }) + ); + + let decoded = serde_json::from_value::(value) + .expect("deserialize device/key/sign response"); + assert_eq!(decoded, response); + } + #[test] fn fs_create_directory_params_round_trip_with_default_recursive() { let params = FsCreateDirectoryParams { diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 7a2ada076..f522908be 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -1186,6 +1186,11 @@ impl CodexMessageProcessor { self.command_exec_terminate(to_connection_request_id(request_id), params) .await; } + ClientRequest::DeviceKeyCreate { .. } + | ClientRequest::DeviceKeyPublic { .. } + | ClientRequest::DeviceKeySign { .. } => { + warn!("Device key request reached CodexMessageProcessor unexpectedly"); + } ClientRequest::ConfigRead { .. } | ClientRequest::ConfigValueWrite { .. } | ClientRequest::ConfigBatchWrite { .. }