xml_sec/provider/key_establishment/
dh.rs1use crypto_bigint::{
5 BoxedUint, NonZero, Odd,
6 modular::{BoxedMontyForm, BoxedMontyParams},
7};
8use crypto_primes::hazmat::MillerRabin;
9use zeroize::Zeroizing;
10
11use crate::provider::{
12 CryptoProvider, KeyAgreementKey, KeyAgreementParameters, ProviderError, ProviderInputError,
13 ProviderOperation,
14};
15use crate::xmlenc::{KeyEstablishmentBudget, XmlEncError};
16
17use super::{DH_ES_URI, DH_URI};
18
19pub struct RustCryptoDhKey {
22 params: BoxedMontyParams,
23 order: BoxedUint,
24 generator: BoxedUint,
25 private: Zeroizing<BoxedUint>,
26 width: usize,
27 domain_bits: (usize, usize),
28}
29
30impl core::fmt::Debug for RustCryptoDhKey {
31 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
32 f.debug_struct("RustCryptoDhKey").finish_non_exhaustive()
33 }
34}
35
36impl RustCryptoDhKey {
37 pub fn from_components(
42 provider: &dyn CryptoProvider,
43 budget: &mut KeyEstablishmentBudget<'_>,
44 p: &[u8],
45 q: &[u8],
46 generator: &[u8],
47 private: &[u8],
48 ) -> Result<Self, XmlEncError> {
49 let ceiling = crate::hard_limits::DH_MODULUS_BIT_CEILING / 8;
50 if p.len() > ceiling
51 || q.len() > p.len()
52 || generator.len() > p.len()
53 || private.len() > p.len()
54 {
55 return Err(invalid().into());
56 }
57 let p = significant(p);
58 let q = significant(q);
59 let generator = significant(generator);
60 let p_bits = bits(p);
61 let q_bits = bits(q);
62 if p_bits < 512
68 || q_bits < 160
69 || q_bits >= p_bits
70 || bits(generator) < 160
71 || p.last().is_none_or(|v| v & 1 == 0)
72 || q.last().is_none_or(|v| v & 1 == 0)
73 {
74 return Err(invalid().into());
75 }
76 budget.reserve_dh_import(p_bits, q_bits, twos_minus_one(p), twos_minus_one(q))?;
77 let precision = p_bits.div_ceil(64) as u32 * 64;
78 let p_value = decode(p, precision)?;
79 let q_small = decode(q, q_bits.div_ceil(64) as u32 * 64)?;
80 let order = decode(q, precision)?;
81 let one = BoxedUint::one_with_precision(precision);
82 let two = decode(&[2], precision)?;
83 let p_minus_one = p_value.wrapping_sub(&one);
84 let divisor = NonZero::new(order.clone()).ok_or_else(invalid)?;
85 if order >= p_minus_one
86 || p_minus_one.rem_vartime(&divisor) != BoxedUint::zero_with_precision(precision)
87 {
88 return Err(invalid().into());
89 }
90 let generator = decode(generator, precision)?;
91 let private = Zeroizing::new(decode(private, precision)?);
92 if generator <= one
93 || generator >= p_minus_one
94 || *private < two
95 || *private > order.wrapping_sub(&two)
96 {
97 return Err(invalid().into());
98 }
99 probable_prime(|bytes| provider.fill_random(bytes), &p_value)?;
100 probable_prime(|bytes| provider.fill_random(bytes), &q_small)?;
101 let params = BoxedMontyParams::new_vartime(Odd::new(p_value).ok_or_else(invalid)?);
102 if BoxedMontyForm::new(generator.clone(), ¶ms)
103 .pow_bounded_exp(&order, q_bits as u32)
104 .retrieve()
105 != one
106 {
107 return Err(invalid().into());
108 }
109 Ok(Self {
110 params,
111 order,
112 generator,
113 private,
114 width: p.len(),
115 domain_bits: (p_bits, q_bits),
116 })
117 }
118
119 pub fn public_key(&self) -> Vec<u8> {
121 let value = Zeroizing::new(
122 BoxedMontyForm::new(self.generator.clone(), &self.params).pow(&self.private),
123 );
124 let integer = Zeroizing::new(value.retrieve());
125 let bytes = Zeroizing::new(integer.to_be_bytes());
126 bytes[bytes.len() - self.width..].to_vec()
127 }
128}
129
130impl KeyAgreementKey for RustCryptoDhKey {
131 fn dh_domain_bits(&self) -> Option<(usize, usize)> {
132 Some(self.domain_bits)
133 }
134
135 fn agree(&self, parameters: &KeyAgreementParameters<'_>) -> Result<Vec<u8>, ProviderError> {
136 if !matches!(parameters.algorithm, DH_URI | DH_ES_URI) {
137 return Err(ProviderError::Unsupported {
138 operation: ProviderOperation::KeyAgreement,
139 algorithm: Some(parameters.algorithm.into()),
140 });
141 }
142 if parameters.peer_public_key.is_empty() || parameters.peer_public_key.len() > self.width {
143 return Err(invalid());
144 }
145 let precision = self.generator.bits_precision();
146 let peer = decode(parameters.peer_public_key, precision)?;
147 let one = BoxedUint::one_with_precision(precision);
148 let two = decode(&[2], precision)?;
149 if peer < two || peer >= self.params.modulus().as_ref().wrapping_sub(&one) {
154 return Err(invalid());
155 }
156 let peer = BoxedMontyForm::new(peer, &self.params);
157 if peer
158 .pow_bounded_exp(&self.order, self.order.bits_vartime())
159 .retrieve()
160 != one
161 {
162 return Err(invalid());
163 }
164 let secret = Zeroizing::new(peer.pow(&self.private));
168 let integer = Zeroizing::new(secret.retrieve());
169 let bytes = Zeroizing::new(integer.to_be_bytes());
170 Ok(bytes[bytes.len() - self.width..].to_vec())
171 }
172}
173
174fn invalid() -> ProviderError {
175 ProviderError::InvalidInput(ProviderInputError::DhKey)
176}
177
178fn significant(bytes: &[u8]) -> &[u8] {
179 let offset = bytes.iter().position(|&v| v != 0).unwrap_or(bytes.len());
180 &bytes[offset..]
181}
182
183fn bits(bytes: &[u8]) -> usize {
184 match bytes.first() {
185 Some(first) => bytes.len() * 8 - first.leading_zeros() as usize,
186 None => 0,
187 }
188}
189
190fn twos_minus_one(bytes: &[u8]) -> usize {
191 let last = bytes[bytes.len() - 1] - 1;
192 if last != 0 {
193 return last.trailing_zeros() as usize;
194 }
195 let mut count = 8;
196 for byte in bytes[..bytes.len() - 1].iter().rev() {
197 if *byte != 0 {
198 return count + byte.trailing_zeros() as usize;
199 }
200 count += 8;
201 }
202 count
203}
204
205fn decode(bytes: &[u8], precision: u32) -> Result<BoxedUint, ProviderError> {
206 BoxedUint::from_be_slice(bytes, precision).map_err(|_| invalid())
207}
208
209fn probable_prime(
210 mut fill_random: impl FnMut(&mut [u8]) -> Result<(), ProviderError>,
211 candidate: &BoxedUint,
212) -> Result<(), ProviderError> {
213 let test = MillerRabin::new(Odd::new(candidate.clone()).ok_or_else(invalid)?);
214 let precision = candidate.bits_precision();
215 let two = decode(&[2], precision)?;
216 let maximum = candidate.wrapping_sub(&two);
217 let maximum_bytes = maximum.to_be_bytes();
218 let mut bytes = vec![0; (candidate.bits_vartime() as usize).div_ceil(8)];
219 let maximum_bytes = &maximum_bytes[maximum_bytes.len() - bytes.len()..];
220 let excess = bytes.len() * 8 - candidate.bits_vartime() as usize;
221 for _ in 0..64 {
225 let mut selected = None;
226 for _ in 0..128 {
227 fill_random(&mut bytes)?;
228 bytes[0] &= 0xff >> excess;
229 let value = significant(&bytes);
230 if (value.len() > 1 || value.first().is_some_and(|v| *v >= 2))
231 && bytes.as_slice() <= maximum_bytes
232 {
233 selected = Some(decode(&bytes, precision)?);
234 break;
235 }
236 }
237 let base =
238 selected.ok_or_else(|| ProviderError::Random("DH base sampling exhausted".into()))?;
239 if test.test(&base).is_composite() {
240 return Err(invalid());
241 }
242 }
243 Ok(())
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249
250 #[test]
251 fn primality_randomness_errors_are_terminal() {
252 let candidate = decode(&[251], 64).expect("prime fixture fits one limb");
254 let mut calls = 0;
255 let result = probable_prime(
256 |_| {
257 calls += 1;
258 Err(ProviderError::AuthenticationFailed)
259 },
260 &candidate,
261 );
262 assert!(matches!(result, Err(ProviderError::AuthenticationFailed)));
263 assert_eq!(calls, 1);
264 }
265
266 #[test]
267 fn unusable_randomness_has_a_finite_rejection_bound() {
268 let candidate = decode(&[251], 64).expect("prime fixture fits one limb");
270 let mut calls = 0;
271 let result = probable_prime(
272 |bytes| {
273 calls += 1;
274 bytes.fill(0);
275 Ok(())
276 },
277 &candidate,
278 );
279 assert!(matches!(result, Err(ProviderError::Random(_))));
280 assert_eq!(calls, 128);
281 }
282
283 #[test]
284 fn primality_rejects_composites_and_samples_all_rounds_for_primes() {
285 for (candidate, expected, count) in [(251, true, 64), (253, false, 1)] {
288 let mut calls = 0;
289 let result = probable_prime(
290 |bytes| {
291 calls += 1;
292 bytes.fill(2);
293 Ok(())
294 },
295 &decode(&[candidate], 64).expect("primality fixtures fit one limb"),
296 );
297 assert_eq!(result.is_ok(), expected);
298 assert_eq!(calls, count);
299 }
300 }
301}