1use crate::document::{
4 DocumentParseSettings, XmlParseWorkBudget, parse_borrowed_with_settings_and_budget,
5};
6use crate::provider::{KdfContext, KdfParameters};
7use crate::xml::dom::Node;
8
9use super::parse::{
10 bounded_simple_text_with_limit, decode_bounded_base64_text, require_element as require,
11 validate_metadata_len,
12};
13use super::types::{XMLDSIG_NS, XMLENC11_NS};
14use super::{XmlEncError, map_document_error};
15
16mod serialize;
17
18const MORE_NS: &str = "http://www.w3.org/2021/04/xmldsig-more#";
19const CONCAT: &str = "http://www.w3.org/2009/xmlenc11#ConcatKDF";
20const PBKDF2: &str = "http://www.w3.org/2009/xmlenc11#pbkdf2";
21const HKDF: &str = "http://www.w3.org/2021/04/xmldsig-more#hkdf";
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum ConcatKdfField {
26 AlgorithmId,
28 PartyUInfo,
30 PartyVInfo,
32 SuppPubInfo,
34 SuppPrivInfo,
36}
37
38#[derive(Debug, Clone, PartialEq, Eq)]
43pub struct KeyDerivationMethod {
44 algorithm: String,
45 digest: String,
46 salt: Vec<u8>,
47 context: Vec<u8>,
48 context_bits: Option<usize>,
49 concat_fields: [Option<(usize, usize)>; 5],
50 iterations: u64,
51 key_length: Option<usize>,
52}
53
54impl KeyDerivationMethod {
55 pub(super) fn validate_metadata(&self, maximum: usize) -> Result<(), XmlEncError> {
56 serialize::validate_metadata(self, maximum)
57 }
58 pub fn to_xml(&self, resources: &crate::policy::ResourcePolicy) -> Result<String, XmlEncError> {
62 serialize::serialize(self, resources)
63 }
64 pub(super) fn hkdf(digest: String, salt: Vec<u8>, context: Vec<u8>, key_length: usize) -> Self {
65 Self {
66 algorithm: HKDF.into(),
67 digest,
68 salt,
69 context,
70 context_bits: None,
71 concat_fields: [None; 5],
72 iterations: 0,
73 key_length: Some(key_length),
74 }
75 }
76 pub fn derive_key(
79 &self,
80 output_len: usize,
81 provider: &dyn crate::provider::CryptoProvider,
82 secret: &[u8],
83 budget: &mut super::KeyEstablishmentBudget<'_>,
84 ) -> Result<zeroize::Zeroizing<Vec<u8>>, XmlEncError> {
85 budget.derive_key(provider, &self.parameters(output_len)?, secret)
86 }
87 pub fn concat_field_bits(
94 &self,
95 field: ConcatKdfField,
96 ) -> Option<impl Iterator<Item = bool> + '_> {
97 let index = match field {
98 ConcatKdfField::AlgorithmId => 0,
99 ConcatKdfField::PartyUInfo => 1,
100 ConcatKdfField::PartyVInfo => 2,
101 ConcatKdfField::SuppPubInfo => 3,
102 ConcatKdfField::SuppPrivInfo => 4,
103 };
104 let (start, length) = self.concat_fields[index]?;
105 Some((start..start + length).map(|bit| self.context[bit / 8] & (1 << (7 - bit % 8)) != 0))
106 }
107 pub fn parameters(&self, output_len: usize) -> Result<KdfParameters<'_>, XmlEncError> {
110 if output_len == 0 || self.key_length.is_some_and(|length| length != output_len) {
111 return Err(invalid(
112 "KDF KeyLength does not match the consuming algorithm",
113 ));
114 }
115 Ok(KdfParameters {
116 algorithm: &self.algorithm,
117 digest: Some(&self.digest),
118 salt: &self.salt,
119 info: match self.context_bits {
120 Some(bit_len) => KdfContext::Bits {
121 bytes: &self.context,
122 bit_len,
123 },
124 None => KdfContext::Octets(&self.context),
125 },
126 iterations: self.iterations,
127 output_len,
128 })
129 }
130}
131
132pub fn parse_key_derivation_method(
136 xml: &str,
137 policy: &crate::policy::DecryptionPolicy,
138) -> Result<KeyDerivationMethod, XmlEncError> {
139 parse_key_derivation_method_with_backend(xml, policy, crate::XmlBackend::default())
140}
141
142pub fn parse_key_derivation_method_with_backend(
144 xml: &str,
145 policy: &crate::policy::DecryptionPolicy,
146 backend: crate::XmlBackend,
147) -> Result<KeyDerivationMethod, XmlEncError> {
148 policy.validate()?;
149 policy.resources.validate_xml_document_len(xml.len())?;
150 let settings =
151 DocumentParseSettings::from_policy(&policy.xml, &policy.resources).with_backend(backend);
152 let budget = XmlParseWorkBudget::from_resources(&policy.resources);
153 let document = parse_borrowed_with_settings_and_budget(xml, settings, Some(&budget))
154 .map_err(|error| map_document_error(error, settings))?;
155 parse_node(
156 document.root_element(),
157 policy.resources.max_encryption_metadata_bytes,
158 )
159}
160
161pub(super) fn parse_node(
162 node: Node<'_, '_>,
163 maximum: usize,
164) -> Result<KeyDerivationMethod, XmlEncError> {
165 require(node, XMLENC11_NS, "KeyDerivationMethod")?;
166 let algorithm = attribute(node, "Algorithm", maximum)?;
167 let parameters = single_child(node)?;
168 element_content(parameters)?;
169 let mut result = KeyDerivationMethod {
170 algorithm: algorithm.to_owned(),
171 digest: String::new(),
172 salt: Vec::new(),
173 context: Vec::new(),
174 context_bits: None,
175 concat_fields: [None; 5],
176 iterations: 0,
177 key_length: None,
178 };
179 match algorithm {
180 CONCAT => {
181 require(parameters, XMLENC11_NS, "ConcatKDFParams")?;
182 result.digest = algorithm_child(
183 single_child(parameters)?,
184 XMLDSIG_NS,
185 "DigestMethod",
186 maximum,
187 )?;
188 let fields = [
192 "AlgorithmID",
193 "PartyUInfo",
194 "PartyVInfo",
195 "SuppPubInfo",
196 "SuppPrivInfo",
197 ]
198 .map(|name| parameters.attribute(name));
199 let (bytes, bits, boundaries) = concat_context(fields, maximum)?;
200 result.context = bytes;
201 result.context_bits = Some(bits);
202 result.concat_fields = boundaries;
203 }
204 PBKDF2 => {
205 require(parameters, XMLENC11_NS, "PBKDF2-params")?;
206 let mut children = parameters.children().filter(Node::is_element);
207 let salt = next(&mut children, XMLENC11_NS, "Salt")?;
208 let specified = single_child(salt)?;
209 if specified.has_tag_name((XMLENC11_NS, "OtherSource")) {
210 return Err(XmlEncError::UnsupportedAlgorithm(
213 attribute(specified, "Algorithm", maximum)?.to_owned(),
214 ));
215 }
216 require(specified, XMLENC11_NS, "Specified")?;
217 result.salt = decode_bounded_base64_text(specified, "Specified", maximum)?;
218 result.iterations = positive(
219 next(&mut children, XMLENC11_NS, "IterationCount")?,
220 "IterationCount",
221 maximum,
222 )?;
223 result.key_length = Some(
227 usize::try_from(positive(
228 next(&mut children, XMLENC11_NS, "KeyLength")?,
229 "KeyLength",
230 maximum,
231 )?)
232 .map_err(|_| invalid("KeyLength exceeds platform width"))?,
233 );
234 result.digest = algorithm_child(
235 next(&mut children, XMLENC11_NS, "PRF")?,
236 XMLENC11_NS,
237 "PRF",
238 maximum,
239 )?;
240 if children.next().is_some() {
241 return Err(invalid("unexpected PBKDF2 parameter"));
242 }
243 }
244 HKDF => {
245 require(parameters, MORE_NS, "HKDFParams")?;
249 let mut children = parameters.children().filter(Node::is_element).peekable();
250 result.digest = algorithm_child(
251 next(&mut children, MORE_NS, "PRF")?,
252 MORE_NS,
253 "PRF",
254 maximum,
255 )?;
256 if children
257 .peek()
258 .is_some_and(|node| node.has_tag_name((MORE_NS, "Salt")))
259 {
260 result.salt = decode_bounded_base64_text(
261 next(&mut children, MORE_NS, "Salt")?,
262 "Salt",
263 maximum,
264 )?;
265 }
266 if children
267 .peek()
268 .is_some_and(|node| node.has_tag_name((MORE_NS, "Info")))
269 {
270 result.context = decode_bounded_base64_text(
271 next(&mut children, MORE_NS, "Info")?,
272 "Info",
273 maximum,
274 )?;
275 }
276 if children
277 .peek()
278 .is_some_and(|node| node.has_tag_name((MORE_NS, "KeyLength")))
279 {
280 result.key_length = Some(
281 usize::try_from(positive(
282 next(&mut children, MORE_NS, "KeyLength")?,
283 "KeyLength",
284 maximum,
285 )?)
286 .map_err(|_| invalid("KeyLength exceeds platform width"))?,
287 );
288 }
289 if children.next().is_some() {
290 return Err(invalid("unexpected HKDF parameter"));
291 }
292 }
293 _ => return Err(XmlEncError::UnsupportedAlgorithm(algorithm.to_owned())),
294 }
295 Ok(result)
296}
297
298fn invalid(message: &str) -> XmlEncError {
299 XmlEncError::InvalidStructure(message.into())
300}
301
302fn single_child<'a>(node: Node<'a, 'a>) -> Result<Node<'a, 'a>, XmlEncError> {
303 element_content(node)?;
304 let mut children = node.children().filter(Node::is_element);
305 let child = children
306 .next()
307 .ok_or(XmlEncError::MissingRequired("KDF parameters"))?;
308 if children.next().is_some() {
309 return Err(invalid("expected exactly one KDF parameter element"));
310 }
311 Ok(child)
312}
313
314fn element_content(node: Node<'_, '_>) -> Result<(), XmlEncError> {
315 for child in node.children() {
316 if child.is_text()
317 && child.text().is_some_and(|text| {
318 !text
319 .bytes()
320 .all(|byte| matches!(byte, b' ' | b'\t' | b'\r' | b'\n'))
321 })
322 {
323 return Err(invalid("KDF parameter container contains character data"));
324 }
325 }
326 Ok(())
327}
328
329fn next<'a>(
330 children: &mut impl Iterator<Item = Node<'a, 'a>>,
331 namespace: &str,
332 name: &'static str,
333) -> Result<Node<'a, 'a>, XmlEncError> {
334 let node = children.next().ok_or(XmlEncError::MissingRequired(name))?;
335 require(node, namespace, name)?;
336 Ok(node)
337}
338
339fn attribute<'a>(node: Node<'a, 'a>, name: &str, maximum: usize) -> Result<&'a str, XmlEncError> {
340 let value = node
341 .attribute(name)
342 .ok_or_else(|| invalid(&format!("missing {name}")))?;
343 validate_metadata_len(value.len(), maximum)?;
344 Ok(value)
345}
346
347fn algorithm_child(
348 node: Node<'_, '_>,
349 namespace: &str,
350 name: &str,
351 maximum: usize,
352) -> Result<String, XmlEncError> {
353 require(node, namespace, name)?;
354 element_content(node)?;
355 if node.children().any(|child| child.is_element()) {
356 return Err(invalid("unsupported digest/PRF parameters"));
357 }
358 Ok(attribute(node, "Algorithm", maximum)?.to_owned())
359}
360
361fn positive(node: Node<'_, '_>, field: &'static str, maximum: usize) -> Result<u64, XmlEncError> {
362 let text = bounded_simple_text_with_limit(node, field, maximum)?;
363 let value = text.trim_matches([' ', '\t', '\r', '\n']);
364 let value = value.strip_prefix('+').unwrap_or(value);
365 if value.is_empty() || !value.bytes().all(|byte| byte.is_ascii_digit()) {
366 return Err(invalid("KDF integer must be positive"));
367 }
368 let number = value
369 .parse::<u64>()
370 .map_err(|_| invalid("KDF integer exceeds u64"))?;
371 if number == 0 {
372 return Err(invalid("KDF integer must be positive"));
373 }
374 Ok(number)
375}
376
377fn nibble(byte: u8) -> Result<u8, XmlEncError> {
378 match byte {
379 b'0'..=b'9' => Ok(byte - b'0'),
380 b'a'..=b'f' => Ok(byte - b'a' + 10),
381 b'A'..=b'F' => Ok(byte - b'A' + 10),
382 _ => Err(invalid("invalid ConcatKDF hexBinary")),
383 }
384}
385
386fn hex_byte(bytes: &[u8]) -> Result<u8, XmlEncError> {
387 Ok(nibble(bytes[0])? * 16 + nibble(bytes[1])?)
388}
389
390fn concat_field(value: &str, maximum: usize) -> Result<(&[u8], usize), XmlEncError> {
391 validate_metadata_len(value.len(), maximum)?;
392 let hex = value.trim_matches([' ', '\t', '\r', '\n']).as_bytes();
393 if hex.is_empty() {
394 return Ok((hex, 0));
395 }
396 if !hex.len().is_multiple_of(2) {
397 return Err(invalid("odd ConcatKDF hexBinary length"));
398 }
399 let padding = hex_byte(&hex[..2])? as usize;
400 let payload = &hex[2..];
401 if padding > 7 || payload.is_empty() && padding != 0 {
402 return Err(invalid("invalid ConcatKDF padding count"));
403 }
404 for pair in payload.as_chunks::<2>().0 {
405 hex_byte(pair)?;
406 }
407 if padding != 0 && hex_byte(&payload[payload.len() - 2..])? & ((1 << padding) - 1) != 0 {
408 return Err(invalid("nonzero ConcatKDF padding bits"));
409 }
410 Ok((payload, payload.len() / 2 * 8 - padding))
411}
412
413type ConcatContext = (Vec<u8>, usize, [Option<(usize, usize)>; 5]);
414
415fn concat_context(fields: [Option<&str>; 5], maximum: usize) -> Result<ConcatContext, XmlEncError> {
416 let mut total = 0usize;
417 let mut boundaries = [None; 5];
418 for (index, field) in fields.iter().enumerate() {
420 let Some(value) = field else {
421 continue;
422 };
423 let (_, bits) = concat_field(value, maximum)?;
424 boundaries[index] = Some((total, bits));
425 total = total
426 .checked_add(bits)
427 .ok_or_else(|| invalid("ConcatKDF context overflow"))?;
428 }
429 validate_metadata_len(total.div_ceil(8), maximum)?;
430 let mut output = vec![0; total.div_ceil(8)];
431 let mut offset = 0;
432 for value in fields.into_iter().flatten() {
433 let (hex, bits) = concat_field(value, maximum)?;
434 let mut remaining = bits;
435 for pair in hex.as_chunks::<2>().0 {
436 let byte = hex_byte(pair)?;
437 let shift = offset % 8;
438 let index = offset / 8;
439 output[index] |= byte >> shift;
440 if shift != 0 && index + 1 < output.len() {
441 output[index + 1] |= byte << (8 - shift);
442 }
443 let consumed = remaining.min(8);
444 offset += consumed;
445 remaining -= consumed;
446 }
447 }
448 Ok((output, total, boundaries))
449}