| // Copyright 2026 The BoringSSL Authors | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // https://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| use alloc::{boxed::Box, vec::Vec}; | |
| use bssl_crypto::{mlkem, x25519}; | |
| use rustls::{ | |
| Error, NamedGroup, PeerMisbehaved, ProtocolVersion, | |
| crypto::{ActiveKeyExchange, CompletedKeyExchange, SharedSecret, SupportedKxGroup}, | |
| }; | |
| /// X25519MLKEM768 key exchange group | |
| pub const X25519MLKEM768: &'static dyn SupportedKxGroup = &X25519Mlkem768Group; | |
| macro_rules! ok_or_invalid_share { | |
| ($e:expr) => { | |
| match $e { | |
| Some(v) => v, | |
| None => return Err(Error::PeerMisbehaved(PeerMisbehaved::InvalidKeyShare)), | |
| } | |
| }; | |
| } | |
| #[derive(Debug)] | |
| struct X25519Mlkem768Group; | |
| impl X25519Mlkem768Group { | |
| fn extract_keys(client_pub_key: &[u8]) -> Option<(mlkem::PublicKey768, x25519::PublicKey)> { | |
| // X25519MLKEM768 transposed the order of concatenation in spite of the naming | |
| let (client_encap_key, client_pub_key) = | |
| client_pub_key.split_at_checked(mlkem::PUBLIC_KEY_BYTES_768)?; | |
| let client_encap_key = mlkem::PublicKey768::parse(client_encap_key)?; | |
| Some((client_encap_key, client_pub_key.try_into().ok()?)) | |
| } | |
| } | |
| impl SupportedKxGroup for X25519Mlkem768Group { | |
| fn start(&self) -> Result<Box<dyn ActiveKeyExchange>, Error> { | |
| let (encap_key, decap_key, _) = mlkem::PrivateKey768::generate(); | |
| let (pub_key, priv_key) = x25519::PrivateKey::generate(); | |
| // X25519MLKEM768 transposed the order of concatenation in spite of the naming | |
| let mut client_share = encap_key; | |
| client_share.extend_from_slice(&pub_key); | |
| Ok(Box::new(X25519MlKem768ActiveKeyExchange { | |
| decap_key, | |
| priv_key, | |
| pub_key, | |
| client_share, | |
| })) | |
| } | |
| // This override is for server side share | |
| fn start_and_complete(&self, client_share: &[u8]) -> Result<CompletedKeyExchange, Error> { | |
| // X25519MLKEM768 transposed the order of concatenation in spite of the naming | |
| let (client_encap_key, client_pub_key) = | |
| ok_or_invalid_share!(Self::extract_keys(client_share)); | |
| let (pub_key, priv_key) = x25519::PrivateKey::generate(); | |
| let dh_secret = ok_or_invalid_share!(priv_key.compute_shared_key(&client_pub_key)); | |
| let (mlkem_ctxt, quantum_secret) = client_encap_key.encapsulate(); | |
| let mut server_share = mlkem_ctxt; | |
| server_share.extend(pub_key); | |
| let mut secret = Vec::with_capacity(mlkem::SHARED_SECRET_BYTES + x25519::SHARED_KEY_LEN); | |
| secret.extend(quantum_secret); | |
| secret.extend(dh_secret); | |
| Ok(CompletedKeyExchange { | |
| group: NamedGroup::X25519MLKEM768, | |
| pub_key: server_share, | |
| secret: secret.into(), | |
| }) | |
| } | |
| fn name(&self) -> NamedGroup { | |
| NamedGroup::X25519MLKEM768 | |
| } | |
| fn usable_for_version(&self, version: ProtocolVersion) -> bool { | |
| matches!(version, ProtocolVersion::TLSv1_3) | |
| } | |
| } | |
| struct X25519MlKem768ActiveKeyExchange { | |
| decap_key: mlkem::PrivateKey768, | |
| priv_key: x25519::PrivateKey, | |
| pub_key: x25519::PublicKey, | |
| client_share: Vec<u8>, | |
| } | |
| impl X25519MlKem768ActiveKeyExchange { | |
| fn compute_dh_share(&self, peer_pub_key: &[u8]) -> Result<[u8; x25519::SHARED_KEY_LEN], Error> { | |
| peer_pub_key | |
| .try_into() | |
| .ok() | |
| .and_then(|peer_pub_key| self.priv_key.compute_shared_key(&peer_pub_key)) | |
| .ok_or(Error::PeerMisbehaved(PeerMisbehaved::InvalidKeyShare)) | |
| } | |
| } | |
| impl ActiveKeyExchange for X25519MlKem768ActiveKeyExchange { | |
| fn complete(self: Box<Self>, peer_pub_key: &[u8]) -> Result<SharedSecret, Error> { | |
| // X25519MLKEM768 transposed the order of concatenation in spite of the naming | |
| let (peer_mlkem_share, peer_x25519_share) = | |
| ok_or_invalid_share!(peer_pub_key.split_at_checked(mlkem::CIPHERTEXT_BYTES_768)); | |
| let quantum_secret = ok_or_invalid_share!(self.decap_key.decapsulate(peer_mlkem_share)); | |
| let dh_secret = self.compute_dh_share(peer_x25519_share)?; | |
| let mut shared_secret = | |
| Vec::with_capacity(mlkem::SHARED_SECRET_BYTES + x25519::SHARED_KEY_LEN); | |
| shared_secret.extend(quantum_secret); | |
| shared_secret.extend(dh_secret); | |
| Ok(shared_secret.into()) | |
| } | |
| fn pub_key(&self) -> &[u8] { | |
| &self.client_share | |
| } | |
| fn hybrid_component(&self) -> Option<(NamedGroup, &[u8])> { | |
| Some((NamedGroup::X25519, &self.pub_key)) | |
| } | |
| fn complete_hybrid_component( | |
| self: Box<Self>, | |
| peer_pub_key: &[u8], | |
| ) -> Result<SharedSecret, Error> { | |
| self.compute_dh_share(peer_pub_key) | |
| .map(|secret| Vec::from(secret).into()) | |
| } | |
| fn group(&self) -> NamedGroup { | |
| NamedGroup::X25519MLKEM768 | |
| } | |
| } |