Skip to main content

xml_sec/provider/
key_encapsulation.rs

1//! Provider-neutral KEM contract. XML orchestration and deployment permission
2//! live outside the primitive; no key handle exposes its secret through Debug.
3
4use super::{CryptoProvider, ProviderBinding, ProviderError};
5use zeroize::Zeroizing;
6
7/// Experimental libxmlsec1 ML-KEM parameter sets (not signature algorithms).
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9pub enum KeyEncapsulationAlgorithm {
10    /// FIPS 203 category 1.
11    MlKem512,
12    /// FIPS 203 category 3.
13    MlKem768,
14    /// FIPS 203 category 5.
15    MlKem1024,
16}
17
18impl KeyEncapsulationAlgorithm {
19    /// Exact libxmlsec1 experimental wire identifier, not a W3C algorithm URI.
20    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    /// Recognize only the exact parameter-set URI.
29    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    /// Raw encapsulation-key length, RFC 9935 Appendix B.
36    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    /// Expanded private-key length, RFC 9935 Appendix B.
45    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    /// Fixed ciphertext length, RFC 9935 Appendix B.
54    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    /// RFC 9935 section 3 AlgorithmIdentifier OID.
63    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    /// Recognize the RFC 9935 parameter-set OID independently of capability.
72    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
79/// One encapsulation result. The secret is always exactly 32 bytes and erased on drop.
80pub struct EncapsulatedKey {
81    /// Public ciphertext to serialize into EncapsulationMechanism.
82    pub ciphertext: Vec<u8>,
83    /// Shared secret; callers retain its erasing owner through the consuming operation.
84    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
95/// Opaque recipient public key. The selected provider supplies randomness.
96pub trait KeyEncapsulationKey: Send + Sync {
97    /// Parameter set of this actual key, not a caller-provided hint.
98    fn algorithm(&self) -> KeyEncapsulationAlgorithm;
99    /// Engine ownership, when an external engine owns this handle.
100    fn provider_binding(&self) -> Option<&ProviderBinding> {
101        None
102    }
103    /// Primitive invoked only by its owning CryptoProvider after operation gates.
104    fn encapsulate_with_provider(
105        &self,
106        provider: &dyn CryptoProvider,
107    ) -> Result<EncapsulatedKey, ProviderError>;
108}
109
110/// Opaque recipient private key. Valid-size ciphertext rejection stays implicit.
111pub trait KeyDecapsulationKey: Send + Sync {
112    /// Parameter set of this actual key.
113    fn algorithm(&self) -> KeyEncapsulationAlgorithm;
114    /// Engine ownership, when an external engine owns this handle.
115    fn provider_binding(&self) -> Option<&ProviderBinding> {
116        None
117    }
118    /// FIPS 203 section 7.3: return the real-or-rejection secret without validity metadata.
119    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        // Compiled capability is exact; an unavailable engine or foreign handle
164        // must fail before either callback, including for a valid-size ciphertext.
165        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        // Prevent URI normalization or parameter-set aliasing at the security boundary.
199        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}