diff --git a/codex-rs/exec-server/src/aws_lc_ml_kem.rs b/codex-rs/exec-server/src/aws_lc_ml_kem.rs index 9c41005627..8cdbea622c 100644 --- a/codex-rs/exec-server/src/aws_lc_ml_kem.rs +++ b/codex-rs/exec-server/src/aws_lc_ml_kem.rs @@ -1,9 +1,4 @@ -//! Bridges AWS-LC's ML-KEM-768 implementation into Clatter's `Kem` trait. -//! -//! Clatter owns the hybrid Noise state machine, while AWS-LC owns the concrete -//! post-quantum operations and their randomness. This module is intentionally -//! limited to converting between the two libraries' fixed-size key, ciphertext, -//! and shared-secret representations; it does not add protocol behavior. +//! Clatter's ML-KEM-768 adapter, backed by AWS-LC. use aws_lc_rs::kem::Ciphertext; use aws_lc_rs::kem::DecapsulationKey; @@ -40,10 +35,6 @@ impl Kem for AwsLcMlKem768 { type Ct = HeapArray; type Ss = SensitiveByteArray<[u8; SHARED_SECRET_LEN]>; - // Generate the long-lived static KEM keypair used by hybrid IK. AWS-LC - // returns serialized key bytes; copying them into Clatter's fixed-size - // containers gives the handshake implementation the exact lengths encoded - // by this suite and keeps the secret half in a zeroizing container. fn genkey_rng(_rng: &mut R) -> KemResult> { // AWS-LC owns ML-KEM key-generation randomness internally, so // Clatter's injectable RNG cannot be plumbed through this provider. @@ -65,9 +56,6 @@ impl Kem for AwsLcMlKem768 { }) } - // Parse the peer's serialized public key at the provider boundary, then - // return the ciphertext and shared secret in Clatter's fixed-size types. - // AWS-LC performs both public-key validation and encapsulation randomness. fn encapsulate(pk: &[u8], _rng: &mut R) -> KemResult<(Self::Ct, Self::Ss)> { let encapsulation_key = EncapsulationKey::new(&ML_KEM_768, pk).map_err(|_| KemError::Input)?; @@ -81,8 +69,6 @@ impl Kem for AwsLcMlKem768 { )) } - // Reconstruct the provider key from process-local secret bytes and recover - // the same shared secret the peer mixed into the hybrid handshake. fn decapsulate(ct: &[u8], sk: &[u8]) -> KemResult { // Reject the length before constructing AWS-LC's ciphertext wrapper. // This keeps malformed wire input classified as an input error rather diff --git a/codex-rs/exec-server/src/noise_channel.rs b/codex-rs/exec-server/src/noise_channel.rs index d60b69cc66..f295171192 100644 --- a/codex-rs/exec-server/src/noise_channel.rs +++ b/codex-rs/exec-server/src/noise_channel.rs @@ -1,29 +1,14 @@ //! Narrow, misuse-resistant wrapper around the Clatter primitives used by the //! remote exec-server relay. //! -//! # Protocol overview +//! The harness initiates hybrid IK and pins the exec-server static key returned +//! by the registry. The first handshake message lets the exec-server authenticate +//! the harness static key; the exec-server then asks the registry whether that +//! key is authorized before completing the handshake. //! -//! Noise is a framework for turning Diffie-Hellman operations into an -//! authenticated handshake and then an encrypted byte channel. This module uses -//! the `IK` handshake pattern: the harness is the initiator and already knows -//! the exec-server's static public key, while the exec-server learns and -//! authenticates the harness's static public key from the first handshake -//! message. That lets the harness reject the wrong executor immediately and -//! gives the executor a cryptographic identity it can authorize with the -//! environment registry. -//! -//! The suite is "hybrid" because the handshake combines classical X25519 with -//! post-quantum ML-KEM-768. Clatter runs the Noise state machine and mixes both -//! key-agreement results into the session keys; AWS-LC supplies the ML-KEM -//! operations. AES-GCM then protects ordered transport records after the two -//! handshake messages complete. -//! -//! The handshake authenticates keys, not product permissions. The first message -//! therefore carries a registry-issued harness authorization inside its -//! encrypted payload. The exec-server pauses after authenticating that message, -//! asks the registry whether the authenticated harness key is allowed, and only -//! then sends the second handshake message and exposes JSON-RPC. Application -//! data is never accepted before both checks pass. +//! "Hybrid" means the session keys include both X25519 and ML-KEM-768 key +//! agreement. Once the two-message handshake finishes, AES-GCM protects the +//! ordered transport records carrying JSON-RPC. use base64::Engine; use base64::engine::general_purpose::STANDARD; @@ -37,10 +22,7 @@ use serde::Serialize; use crate::aws_lc_ml_kem::AwsLcMlKem768; -/// Stable identifier for the complete handshake and transport algorithm suite. -/// -/// This value travels with public keys so configuration cannot silently combine -/// key material generated for a different Noise pattern or algorithm set. +/// Identifies the handshake pattern and algorithms used by this channel. pub const NOISE_CHANNEL_SUITE: &str = "Noise_hybridIK_X25519+MLKEM768_AESGCM_SHA256"; type DhKeyPair = KeyPair<::PubKey, ::PrivateKey>; @@ -61,9 +43,6 @@ pub struct NoiseChannelPublicKey { impl std::fmt::Debug for NoiseChannelPublicKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // Public keys are not secrets, but logging complete identities makes - // correlation across environments unnecessarily easy. Keep only the - // suite visible in routine diagnostics. f.debug_struct("NoiseChannelPublicKey") .field("suite", &self.suite) .field("x25519_public_key", &"") @@ -73,10 +52,7 @@ impl std::fmt::Debug for NoiseChannelPublicKey { } impl NoiseChannelPublicKey { - /// Serialize both public components as one suite-tagged registry value. - /// - /// Keeping the components together prevents callers from accidentally - /// pairing an X25519 key from one identity with an ML-KEM key from another. + /// Serialize both public components as one suite-tagged value. fn from_keypairs(dh: &DhKeyPair, kem: &KemKeyPair) -> Self { Self { suite: NOISE_CHANNEL_SUITE.to_string(), @@ -98,9 +74,6 @@ pub struct NoiseChannelIdentity { impl std::fmt::Debug for NoiseChannelIdentity { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // Never delegate to the keypair debug implementations: this type owns - // both private keys. Its public projection is sufficient to identify - // which endpoint identity a log entry refers to. f.debug_struct("NoiseChannelIdentity") .field("public_key", &self.public_key()) .finish_non_exhaustive()