xml_sec/provider/
key_encapsulation.rs1use super::{CryptoProvider, ProviderBinding, ProviderError};
5use zeroize::Zeroizing;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9pub enum KeyEncapsulationAlgorithm {
10 MlKem512,
12 MlKem768,
14 MlKem1024,
16}
17
18impl KeyEncapsulationAlgorithm {
19 pub const fn uri(self) -> &'static str {
21 match self {
22 Self::MlKem512 => "http://www.aleksey.com/xmlsec/2025/12/xmldsig-more#ml-kem-512",
23 Self::MlKem768 => "http://www.aleksey.com/xmlsec/2025/12/xmldsig-more#ml-kem-768",
24 Self::MlKem1024 => "http://www.aleksey.com/xmlsec/2025/12/xmldsig-more#ml-kem-1024",
25 }
26 }
27
28 pub fn from_uri(uri: &str) -> Option<Self> {
30 [Self::MlKem512, Self::MlKem768, Self::MlKem1024]
31 .into_iter()
32 .find(|algorithm| algorithm.uri() == uri)
33 }
34
35 pub const fn public_key_len(self) -> usize {
37 match self {
38 Self::MlKem512 => 800,
39 Self::MlKem768 => 1184,
40 Self::MlKem1024 => 1568,
41 }
42 }
43
44 pub const fn expanded_key_len(self) -> usize {
46 match self {
47 Self::MlKem512 => 1632,
48 Self::MlKem768 => 2400,
49 Self::MlKem1024 => 3168,
50 }
51 }
52
53 pub const fn ciphertext_len(self) -> usize {
55 match self {
56 Self::MlKem512 => 768,
57 Self::MlKem768 => 1088,
58 Self::MlKem1024 => 1568,
59 }
60 }
61
62 pub const fn oid(self) -> pkcs8::ObjectIdentifier {
64 match self {
65 Self::MlKem512 => pkcs8::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.4.1"),
66 Self::MlKem768 => pkcs8::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.4.2"),
67 Self::MlKem1024 => pkcs8::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.4.3"),
68 }
69 }
70
71 pub fn from_oid(oid: pkcs8::ObjectIdentifier) -> Option<Self> {
73 [Self::MlKem512, Self::MlKem768, Self::MlKem1024]
74 .into_iter()
75 .find(|algorithm| algorithm.oid() == oid)
76 }
77}
78
79pub struct EncapsulatedKey {
81 pub ciphertext: Vec<u8>,
83 pub shared_secret: Zeroizing<[u8; 32]>,
85}
86
87impl core::fmt::Debug for EncapsulatedKey {
88 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
89 f.debug_struct("EncapsulatedKey")
90 .field("ciphertext_len", &self.ciphertext.len())
91 .finish_non_exhaustive()
92 }
93}
94
95pub trait KeyEncapsulationKey: Send + Sync {
97 fn algorithm(&self) -> KeyEncapsulationAlgorithm;
99 fn provider_binding(&self) -> Option<&ProviderBinding> {
101 None
102 }
103 fn encapsulate_with_provider(
105 &self,
106 provider: &dyn CryptoProvider,
107 ) -> Result<EncapsulatedKey, ProviderError>;
108}
109
110pub trait KeyDecapsulationKey: Send + Sync {
112 fn algorithm(&self) -> KeyEncapsulationAlgorithm;
114 fn provider_binding(&self) -> Option<&ProviderBinding> {
116 None
117 }
118 fn decapsulate(&self, ciphertext: &[u8]) -> Result<Zeroizing<[u8; 32]>, ProviderError>;
120}
121
122#[cfg(feature = "experimental-pq")]
123mod rustcrypto;
124#[cfg(feature = "experimental-pq")]
125pub use rustcrypto::{
126 MlKemPrivateKeyEncoding, RustCryptoMlKemPrivateKey, RustCryptoMlKemPublicKey,
127};
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132 use crate::provider::{ExternalProviderError, ProviderCapability, RustCryptoProvider};
133
134 struct ForeignKey(ProviderBinding);
135 impl KeyEncapsulationKey for ForeignKey {
136 fn algorithm(&self) -> KeyEncapsulationAlgorithm {
137 KeyEncapsulationAlgorithm::MlKem768
138 }
139 fn provider_binding(&self) -> Option<&ProviderBinding> {
140 Some(&self.0)
141 }
142 fn encapsulate_with_provider(
143 &self,
144 _: &dyn CryptoProvider,
145 ) -> Result<EncapsulatedKey, ProviderError> {
146 panic!("foreign key must not execute")
147 }
148 }
149 impl KeyDecapsulationKey for ForeignKey {
150 fn algorithm(&self) -> KeyEncapsulationAlgorithm {
151 KeyEncapsulationAlgorithm::MlKem768
152 }
153 fn provider_binding(&self) -> Option<&ProviderBinding> {
154 Some(&self.0)
155 }
156 fn decapsulate(&self, _: &[u8]) -> Result<Zeroizing<[u8; 32]>, ProviderError> {
157 panic!("foreign key must not execute")
158 }
159 }
160
161 #[test]
162 fn capability_and_binding_gate_both_callbacks() {
163 let algorithm = KeyEncapsulationAlgorithm::MlKem768;
166 assert_eq!(
167 RustCryptoProvider.supports(ProviderCapability::Encapsulate(algorithm)),
168 cfg!(feature = "experimental-pq")
169 );
170 assert_eq!(
171 RustCryptoProvider.supports(ProviderCapability::Decapsulate(algorithm)),
172 cfg!(feature = "experimental-pq")
173 );
174 let key = ForeignKey(ProviderBinding::default());
175 let encapsulation = RustCryptoProvider
176 .encapsulate_key(&key)
177 .expect_err("foreign key");
178 let decapsulation = RustCryptoProvider
179 .decapsulate_key(&key, &vec![0; algorithm.ciphertext_len()])
180 .expect_err("foreign key");
181 if cfg!(feature = "experimental-pq") {
182 assert_eq!(
183 encapsulation,
184 ProviderError::External(ExternalProviderError::Binding)
185 );
186 assert_eq!(
187 decapsulation,
188 ProviderError::External(ExternalProviderError::Binding)
189 );
190 } else {
191 assert!(matches!(encapsulation, ProviderError::Unsupported { .. }));
192 assert!(matches!(decapsulation, ProviderError::Unsupported { .. }));
193 }
194 }
195
196 #[test]
197 fn parameter_identity_is_exact() {
198 for algorithm in [
200 KeyEncapsulationAlgorithm::MlKem512,
201 KeyEncapsulationAlgorithm::MlKem768,
202 KeyEncapsulationAlgorithm::MlKem1024,
203 ] {
204 assert_eq!(
205 KeyEncapsulationAlgorithm::from_uri(algorithm.uri()),
206 Some(algorithm)
207 );
208 assert_eq!(
209 KeyEncapsulationAlgorithm::from_uri(&format!("{} ", algorithm.uri())),
210 None
211 );
212 assert_ne!(
213 algorithm.oid(),
214 pkcs8::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.3.1")
215 );
216 }
217 }
218}