use super::{CryptoProvider, ProviderBinding, ProviderError};
use zeroize::Zeroizing;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum KeyEncapsulationAlgorithm {
MlKem512,
MlKem768,
MlKem1024,
}
impl KeyEncapsulationAlgorithm {
pub const fn uri(self) -> &'static str {
match self {
Self::MlKem512 => "http://www.aleksey.com/xmlsec/2025/12/xmldsig-more#ml-kem-512",
Self::MlKem768 => "http://www.aleksey.com/xmlsec/2025/12/xmldsig-more#ml-kem-768",
Self::MlKem1024 => "http://www.aleksey.com/xmlsec/2025/12/xmldsig-more#ml-kem-1024",
}
}
pub fn from_uri(uri: &str) -> Option<Self> {
[Self::MlKem512, Self::MlKem768, Self::MlKem1024]
.into_iter()
.find(|algorithm| algorithm.uri() == uri)
}
pub const fn public_key_len(self) -> usize {
match self {
Self::MlKem512 => 800,
Self::MlKem768 => 1184,
Self::MlKem1024 => 1568,
}
}
pub const fn expanded_key_len(self) -> usize {
match self {
Self::MlKem512 => 1632,
Self::MlKem768 => 2400,
Self::MlKem1024 => 3168,
}
}
pub const fn ciphertext_len(self) -> usize {
match self {
Self::MlKem512 => 768,
Self::MlKem768 => 1088,
Self::MlKem1024 => 1568,
}
}
pub const fn oid(self) -> pkcs8::ObjectIdentifier {
match self {
Self::MlKem512 => pkcs8::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.4.1"),
Self::MlKem768 => pkcs8::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.4.2"),
Self::MlKem1024 => pkcs8::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.4.3"),
}
}
pub fn from_oid(oid: pkcs8::ObjectIdentifier) -> Option<Self> {
[Self::MlKem512, Self::MlKem768, Self::MlKem1024]
.into_iter()
.find(|algorithm| algorithm.oid() == oid)
}
}
pub struct EncapsulatedKey {
pub ciphertext: Vec<u8>,
pub shared_secret: Zeroizing<[u8; 32]>,
}
impl core::fmt::Debug for EncapsulatedKey {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("EncapsulatedKey")
.field("ciphertext_len", &self.ciphertext.len())
.finish_non_exhaustive()
}
}
pub trait KeyEncapsulationKey: Send + Sync {
fn algorithm(&self) -> KeyEncapsulationAlgorithm;
fn provider_binding(&self) -> Option<&ProviderBinding> {
None
}
fn encapsulate_with_provider(
&self,
provider: &dyn CryptoProvider,
) -> Result<EncapsulatedKey, ProviderError>;
}
pub trait KeyDecapsulationKey: Send + Sync {
fn algorithm(&self) -> KeyEncapsulationAlgorithm;
fn provider_binding(&self) -> Option<&ProviderBinding> {
None
}
fn decapsulate(&self, ciphertext: &[u8]) -> Result<Zeroizing<[u8; 32]>, ProviderError>;
}
#[cfg(feature = "experimental-pq")]
mod rustcrypto;
#[cfg(feature = "experimental-pq")]
pub use rustcrypto::{
MlKemPrivateKeyEncoding, RustCryptoMlKemPrivateKey, RustCryptoMlKemPublicKey,
};
#[cfg(test)]
mod tests {
use super::*;
use crate::provider::{ExternalProviderError, ProviderCapability, RustCryptoProvider};
struct ForeignKey(ProviderBinding);
impl KeyEncapsulationKey for ForeignKey {
fn algorithm(&self) -> KeyEncapsulationAlgorithm {
KeyEncapsulationAlgorithm::MlKem768
}
fn provider_binding(&self) -> Option<&ProviderBinding> {
Some(&self.0)
}
fn encapsulate_with_provider(
&self,
_: &dyn CryptoProvider,
) -> Result<EncapsulatedKey, ProviderError> {
panic!("foreign key must not execute")
}
}
impl KeyDecapsulationKey for ForeignKey {
fn algorithm(&self) -> KeyEncapsulationAlgorithm {
KeyEncapsulationAlgorithm::MlKem768
}
fn provider_binding(&self) -> Option<&ProviderBinding> {
Some(&self.0)
}
fn decapsulate(&self, _: &[u8]) -> Result<Zeroizing<[u8; 32]>, ProviderError> {
panic!("foreign key must not execute")
}
}
#[test]
fn capability_and_binding_gate_both_callbacks() {
let algorithm = KeyEncapsulationAlgorithm::MlKem768;
assert_eq!(
RustCryptoProvider.supports(ProviderCapability::Encapsulate(algorithm)),
cfg!(feature = "experimental-pq")
);
assert_eq!(
RustCryptoProvider.supports(ProviderCapability::Decapsulate(algorithm)),
cfg!(feature = "experimental-pq")
);
let key = ForeignKey(ProviderBinding::default());
let encapsulation = RustCryptoProvider
.encapsulate_key(&key)
.expect_err("foreign key");
let decapsulation = RustCryptoProvider
.decapsulate_key(&key, &vec![0; algorithm.ciphertext_len()])
.expect_err("foreign key");
if cfg!(feature = "experimental-pq") {
assert_eq!(
encapsulation,
ProviderError::External(ExternalProviderError::Binding)
);
assert_eq!(
decapsulation,
ProviderError::External(ExternalProviderError::Binding)
);
} else {
assert!(matches!(encapsulation, ProviderError::Unsupported { .. }));
assert!(matches!(decapsulation, ProviderError::Unsupported { .. }));
}
}
#[test]
fn parameter_identity_is_exact() {
for algorithm in [
KeyEncapsulationAlgorithm::MlKem512,
KeyEncapsulationAlgorithm::MlKem768,
KeyEncapsulationAlgorithm::MlKem1024,
] {
assert_eq!(
KeyEncapsulationAlgorithm::from_uri(algorithm.uri()),
Some(algorithm)
);
assert_eq!(
KeyEncapsulationAlgorithm::from_uri(&format!("{} ", algorithm.uri())),
None
);
assert_ne!(
algorithm.oid(),
pkcs8::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.3.1")
);
}
}
}