blob: 45169a6bf2ca132ab805d8fe3377e7fd162c93e2 [file] [log] [blame]
// 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
}
}