1use super::parse::{bounded_simple_text_with_limit, require_element, validate_metadata_len};
4use super::types::{XMLDSIG_NS, XMLENC_NS};
5use super::{KeyDerivationMethod, KeyEstablishmentBudget, XmlEncError, map_document_error};
6use crate::document::{
7 DocumentParseSettings, XmlParseWorkBudget, parse_borrowed_with_settings_and_budget,
8};
9use crate::xml::dom::Node;
10
11const MORE: &str = "http://www.w3.org/2021/04/xmldsig-more#";
12const HKDF: &str = "http://www.w3.org/2021/04/xmldsig-more#hkdf";
13
14pub struct HkdfAgreement {
20 method: KeyDerivationMethod,
21 ikm: Option<zeroize::Zeroizing<Vec<u8>>>,
22}
23
24impl core::fmt::Debug for HkdfAgreement {
25 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
26 formatter
27 .debug_struct("HkdfAgreement")
28 .field("method", &self.method)
29 .field("has_embedded_ikm", &self.ikm.is_some())
30 .finish()
31 }
32}
33
34impl HkdfAgreement {
35 pub fn key_derivation_method(&self) -> &KeyDerivationMethod {
37 &self.method
38 }
39
40 pub fn derive_key(
43 &self,
44 output_len: usize,
45 external_ikm: Option<&[u8]>,
46 provider: &dyn crate::provider::CryptoProvider,
47 budget: &mut KeyEstablishmentBudget<'_>,
48 ) -> Result<zeroize::Zeroizing<Vec<u8>>, XmlEncError> {
49 let ikm = match (&self.ikm, external_ikm) {
50 (Some(ikm), None) => ikm.as_slice(),
51 (None, Some(ikm)) => ikm,
52 (None, None) => return Err(XmlEncError::MissingRequired("HKDF initial key material")),
53 (Some(_), Some(_)) => return Err(invalid("HKDF initial key material supplied twice")),
54 };
55 self.method.derive_key(output_len, provider, ikm, budget)
56 }
57}
58
59pub fn parse_hkdf_agreement_method(
62 xml: &str,
63 policy: &crate::policy::DecryptionPolicy,
64 backend: crate::XmlBackend,
65) -> Result<HkdfAgreement, XmlEncError> {
66 policy.validate()?;
67 policy.resources.validate_xml_document_len(xml.len())?;
68 let settings =
69 DocumentParseSettings::from_policy(&policy.xml, &policy.resources).with_backend(backend);
70 let budget = XmlParseWorkBudget::from_resources(&policy.resources);
71 let document = parse_borrowed_with_settings_and_budget(xml, settings, Some(&budget))
72 .map_err(|error| map_document_error(error, settings))?;
73 parse_node(
74 document.root_element(),
75 policy.resources.max_encryption_metadata_bytes,
76 )
77}
78
79pub(super) fn parse_node(node: Node<'_, '_>, maximum: usize) -> Result<HkdfAgreement, XmlEncError> {
80 require_element(node, XMLENC_NS, "AgreementMethod")?;
81 let algorithm = node
86 .attribute("Algorithm")
87 .ok_or(XmlEncError::MissingRequired("AgreementMethod Algorithm"))?;
88 validate_metadata_len(algorithm.len(), maximum)?;
89 if algorithm != HKDF {
90 return Err(XmlEncError::UnsupportedAlgorithm(algorithm.into()));
91 }
92 let mut digest = None;
93 let mut salt = None;
94 let mut info = None;
95 let mut ikm = None;
96 let mut size = None;
97 for child in node.children() {
98 if child.is_text()
99 && child
100 .text()
101 .is_some_and(|text| !text.bytes().all(xml_space))
102 {
103 return Err(invalid("HKDF AgreementMethod contains character data"));
104 }
105 if !child.is_element() {
106 continue;
107 }
108 match (child.tag_name().namespace(), child.tag_name().name()) {
109 (Some(XMLDSIG_NS), "DigestMethod") if digest.is_none() => {
110 if child.children().any(|node| {
111 node.is_element()
112 || (node.is_text()
113 && node.text().is_some_and(|text| !text.bytes().all(xml_space)))
114 }) {
115 return Err(invalid("HKDF DigestMethod parameters are unsupported"));
116 }
117 let value = child
118 .attribute("Algorithm")
119 .ok_or(XmlEncError::MissingRequired("DigestMethod Algorithm"))?;
120 validate_metadata_len(value.len(), maximum)?;
121 use crate::xmldsig::{DigestAlgorithm as D, SignatureAlgorithm as S};
126 let prf = match D::from_uri(value) {
127 Some(D::Sha1) => S::HmacSha1,
128 Some(D::Sha224) => S::HmacSha224,
129 Some(D::Sha256) => S::HmacSha256,
130 Some(D::Sha384) => S::HmacSha384,
131 Some(D::Sha512) => S::HmacSha512,
132 _ => match S::from_uri(value) {
133 Some(
134 prf @ (S::HmacSha1
135 | S::HmacSha224
136 | S::HmacSha256
137 | S::HmacSha384
138 | S::HmacSha512),
139 ) => prf,
140 _ => return Err(XmlEncError::UnsupportedAlgorithm(value.into())),
141 },
142 };
143 digest = Some(prf.uri().to_owned());
144 }
145 (Some(MORE), "Salt") if salt.is_none() => salt = Some(hex_text(child, maximum)?),
146 (Some(XMLENC_NS), "KA-Nonce") if info.is_none() => {
147 info = Some(hex_text(child, maximum)?)
148 }
149 (Some(XMLENC_NS), "OriginatorKeyInfo") if ikm.is_none() => {
150 ikm = Some(zeroize::Zeroizing::new(hex_text(child, maximum)?))
151 }
152 (Some(XMLENC_NS), "KeySize") if size.is_none() => {
153 let text = bounded_simple_text_with_limit(child, "HKDF KeySize", maximum)?;
157 let text = trim_xml_space(&text);
158 if text.is_empty() || !text.bytes().all(|byte| byte.is_ascii_digit()) {
159 return Err(invalid(
160 "HKDF KeySize must be a positive decimal octet count",
161 ));
162 }
163 size = Some(
164 text.parse::<usize>()
165 .ok()
166 .filter(|value| *value != 0)
167 .ok_or_else(|| invalid("HKDF KeySize exceeds platform width or is zero"))?,
168 );
169 }
170 _ => {
171 return Err(invalid(
172 "unexpected or duplicate HKDF AgreementMethod parameter",
173 ));
174 }
175 }
176 }
177 let method = KeyDerivationMethod::hkdf(
181 digest.ok_or(XmlEncError::MissingRequired("HKDF DigestMethod"))?,
182 salt.unwrap_or_default(),
183 info.unwrap_or_default(),
184 size.ok_or(XmlEncError::MissingRequired("HKDF KeySize"))?,
185 );
186 Ok(HkdfAgreement { method, ikm })
187}
188
189fn xml_space(byte: u8) -> bool {
190 matches!(byte, b' ' | b'\t' | b'\r' | b'\n')
191}
192fn trim_xml_space(text: &str) -> &str {
193 text.trim_matches(|value| matches!(value, ' ' | '\t' | '\r' | '\n'))
194}
195fn invalid(message: &str) -> XmlEncError {
196 XmlEncError::InvalidStructure(message.into())
197}
198
199fn hex_text(node: Node<'_, '_>, maximum: usize) -> Result<Vec<u8>, XmlEncError> {
200 let mut text_bytes = 0usize;
204 let mut digits = 0usize;
205 let mut trailing_space = false;
206 for child in node.children() {
207 if child.is_element() {
208 return Err(invalid("HKDF hex parameter contains an element"));
209 }
210 if !child.is_text() {
211 continue;
212 }
213 let text = child.text().unwrap_or_default();
214 text_bytes = text_bytes
215 .checked_add(text.len())
216 .ok_or_else(|| invalid("HKDF hex parameter length overflow"))?;
217 validate_metadata_len(text_bytes, maximum)?;
218 for byte in text.bytes() {
219 if xml_space(byte) {
220 if digits != 0 {
221 trailing_space = true;
222 }
223 } else if byte.is_ascii_hexdigit() && !trailing_space {
224 digits += 1;
225 } else {
226 return Err(invalid("HKDF parameter must be hexadecimal octets"));
227 }
228 }
229 }
230 if !digits.is_multiple_of(2) {
231 return Err(invalid("HKDF parameter must be hexadecimal octets"));
232 }
233 let mut bytes = Vec::with_capacity(digits / 2);
234 let digit = |value: u8| {
235 if value <= b'9' {
236 value - b'0'
237 } else {
238 (value | 32) - b'a' + 10
239 }
240 };
241 let mut high = None;
242 for child in node.children().filter(Node::is_text) {
243 for byte in child.text().unwrap_or_default().bytes() {
244 if xml_space(byte) {
245 continue;
246 }
247 match high.take() {
248 Some(value) => bytes.push(value | digit(byte)),
249 None => high = Some(digit(byte) << 4),
250 }
251 }
252 }
253 debug_assert!(high.is_none());
254 Ok(bytes)
255}