Skip to main content

xml_sec/xmlenc/
agreement.rs

1//! Agreement metadata remains separate from application-owned private keys.
2
3use crate::policy::{KeyAgreementAlgorithm, ResourcePolicy};
4use crate::provider::{KdfContext, KdfParameters, KeyAgreementParameters};
5use crate::xml::dom::Node;
6use crate::xmldsig::parse::{KeyInfo, KeyInfoParsingSession};
7
8use super::parse::{decode_bounded_base64_text, require_element, validate_metadata_len};
9use super::types::{XMLDSIG_NS, XMLENC_NS, XMLENC11_NS};
10use super::{KeyDerivationMethod, XmlEncError};
11
12/// Public agreement descriptor. Key information is advisory; a document never
13/// supplies the application's private handle or chooses its security profile.
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct AgreementMethod {
16    /// Exact agreement mechanism, independently permission checked at execution.
17    pub algorithm: KeyAgreementAlgorithm,
18    /// Explicit derivation; omission requires application knowledge except for
19    /// the legacy DH algorithm, whose derivation is specified by its URI.
20    pub method: Option<KeyDerivationMethod>,
21    /// Public agreement nonce, retained in the trusted descriptor comparison.
22    /// Legacy DH incorporates it directly; explicit KDFs use their own context
23    /// fields rather than an undocumented nonce concatenation.
24    pub nonce: Vec<u8>,
25    /// Optional legacy DH digest. No default digest is guessed.
26    pub legacy_digest: Option<String>,
27    /// Originator public key or application lookup hints in the original role.
28    pub originator: Option<KeyInfo>,
29    /// Recipient lookup hints, never permission to substitute a private key.
30    pub recipient: Option<KeyInfo>,
31}
32
33impl AgreementMethod {
34    /// Establish a consuming key with explicitly bound private and peer keys.
35    /// The caller validates party identities before invoking this method.
36    pub fn derive_key(
37        &self,
38        consuming_algorithm: &str,
39        output_len: usize,
40        key: &dyn crate::provider::KeyAgreementKey,
41        peer: &[u8],
42        provider: &dyn crate::provider::CryptoProvider,
43        budget: &mut super::KeyEstablishmentBudget<'_>,
44    ) -> Result<zeroize::Zeroizing<Vec<u8>>, XmlEncError> {
45        let agreement = KeyAgreementParameters {
46            algorithm: self.algorithm.uri(),
47            peer_public_key: peer,
48        };
49        let parameters = self.parameters(consuming_algorithm, output_len)?;
50        budget.agree_and_derive(provider, key, &agreement, &parameters)
51    }
52
53    pub(super) fn parameters<'a>(
54        &'a self,
55        consuming_algorithm: &'a str,
56        output_len: usize,
57    ) -> Result<KdfParameters<'a>, XmlEncError> {
58        if self.algorithm == KeyAgreementAlgorithm::LegacyDh {
59            // XMLEnc 1.1 §5.6.2.2 binds EncryptionMethod's URI, not the
60            // agreement URI, and its nonce/decimal consuming key width.
61            // https://www.w3.org/TR/2013/REC-xmlenc-core1-20130411/#sec-DHKeyAgreementLegacyKDF
62            return Ok(KdfParameters {
63                algorithm: self.algorithm.uri(),
64                digest: self.legacy_digest.as_deref(),
65                salt: &[],
66                info: KdfContext::LegacyDh {
67                    encryption_algorithm: consuming_algorithm,
68                    nonce: &self.nonce,
69                },
70                iterations: 0,
71                output_len,
72            });
73        }
74        // XMLEnc 1.1 sections 5.4.1 and 5.6 define explicit KDF inputs but
75        // do not define an additional KA-Nonce-to-OtherInfo concatenation.
76        // Preserve the nonce in descriptor identity; applications requiring
77        // fresh explicit-KDF output must also vary its declared context/salt.
78        // https://www.w3.org/TR/2013/REC-xmlenc-core1-20130411/#sec-ConcatKDF
79        // https://www.w3.org/TR/2013/REC-xmlenc-core1-20130411/#sec-Alg-KeyAgreement
80        self.method
81            .as_ref()
82            .ok_or(XmlEncError::MissingRequired(
83                "agreement key derivation method",
84            ))?
85            .parameters(output_len)
86    }
87}
88
89pub(super) fn parse(
90    node: Node<'_, '_>,
91    resources: &ResourcePolicy,
92    key_info: &mut KeyInfoParsingSession<'_>,
93    provider: &dyn crate::provider::CryptoProvider,
94) -> Result<AgreementMethod, XmlEncError> {
95    require_element(node, XMLENC_NS, "AgreementMethod")?;
96    let algorithm = node
97        .attribute("Algorithm")
98        .ok_or(XmlEncError::MissingRequired("AgreementMethod Algorithm"))?;
99    validate_metadata_len(algorithm.len(), resources.max_encryption_metadata_bytes)?;
100    let algorithm = KeyAgreementAlgorithm::from_uri(algorithm)
101        .ok_or_else(|| XmlEncError::UnsupportedAlgorithm(algorithm.into()))?;
102    let mut result = AgreementMethod {
103        algorithm,
104        method: None,
105        nonce: Vec::new(),
106        legacy_digest: None,
107        originator: None,
108        recipient: None,
109    };
110    let mut phase = 0;
111    let mut nonce_seen = false;
112    // XMLEnc 1.1 §5.6: AgreementMethodType is mixed=true. Only element
113    // sequence is constrained; non-whitespace text is not a schema error.
114    // Unlike libxmlsec1 KAMRead, the two KeyInfoType roles are optional.
115    // https://www.w3.org/TR/2013/REC-xmlenc-core1-20130411/#sec-Alg-KeyAgreement
116    for child in node.children().filter(Node::is_element) {
117        match (child.tag_name().namespace(), child.tag_name().name()) {
118            (Some(XMLENC_NS), "KA-Nonce") if phase == 0 && !nonce_seen => {
119                result.nonce = decode_bounded_base64_text(
120                    child,
121                    "KA-Nonce",
122                    resources.max_encryption_metadata_bytes,
123                )?;
124                nonce_seen = true;
125            }
126            (Some(XMLENC11_NS), "KeyDerivationMethod") if phase <= 1 && result.method.is_none() => {
127                result.method = Some(super::key_derivation::parse_node(
128                    child,
129                    resources.max_encryption_metadata_bytes,
130                )?);
131                phase = 1;
132            }
133            (Some(XMLDSIG_NS), "DigestMethod") if phase <= 1 && result.legacy_digest.is_none() => {
134                if algorithm != KeyAgreementAlgorithm::LegacyDh
135                    || child.children().any(|node| node.is_element())
136                {
137                    return Err(invalid(
138                        "DigestMethod is only defined for legacy DH agreement",
139                    ));
140                }
141                let digest = child
142                    .attribute("Algorithm")
143                    .ok_or(XmlEncError::MissingRequired("DigestMethod Algorithm"))?;
144                validate_metadata_len(digest.len(), resources.max_encryption_metadata_bytes)?;
145                result.legacy_digest = Some(digest.into());
146                phase = 1;
147            }
148            (Some(XMLENC_NS), "OriginatorKeyInfo") if phase <= 1 => {
149                key_info
150                    .preflight_agreement_role_candidates(child)
151                    .map_err(map_key_info_error)?;
152                preflight_role_metadata(child, resources.max_encryption_metadata_bytes)?;
153                result.originator = Some(
154                    key_info
155                        .parse_agreement_role(child, provider)
156                        .map_err(map_key_info_error)?,
157                );
158                phase = 2;
159            }
160            (Some(XMLENC_NS), "RecipientKeyInfo") if phase <= 2 => {
161                key_info
162                    .preflight_agreement_role_candidates(child)
163                    .map_err(map_key_info_error)?;
164                preflight_role_metadata(child, resources.max_encryption_metadata_bytes)?;
165                result.recipient = Some(
166                    key_info
167                        .parse_agreement_role(child, provider)
168                        .map_err(map_key_info_error)?,
169                );
170                phase = 3;
171            }
172            (Some(namespace), _) if namespace != XMLENC_NS && phase <= 1 => {
173                // The schema's ##other extension point permits unrelated
174                // metadata. Recognized duplicate parameters must not fall here.
175                if child.has_tag_name((XMLENC11_NS, "KeyDerivationMethod"))
176                    || child.has_tag_name((XMLDSIG_NS, "DigestMethod"))
177                {
178                    return Err(invalid("duplicate agreement parameter"));
179                }
180                phase = 1;
181            }
182            _ => return Err(invalid("unexpected or unordered AgreementMethod child")),
183        }
184    }
185    if algorithm == KeyAgreementAlgorithm::LegacyDh && result.method.is_some() {
186        return Err(invalid("legacy DH URI fixes the derivation algorithm"));
187    }
188    Ok(result)
189}
190
191fn invalid(message: &str) -> XmlEncError {
192    XmlEncError::InvalidStructure(message.into())
193}
194
195fn preflight_role_metadata(node: Node<'_, '_>, maximum: usize) -> Result<(), XmlEncError> {
196    for child in node.descendants().filter(Node::is_element) {
197        for attribute in child.attributes() {
198            validate_metadata_len(attribute.value().len(), maximum)?;
199        }
200        if child.children().any(|node| node.is_element()) {
201            continue;
202        }
203        let binary = matches!(
204            (child.tag_name().namespace(), child.tag_name().name()),
205            (
206                Some(XMLDSIG_NS),
207                "Modulus"
208                    | "Exponent"
209                    | "P"
210                    | "Q"
211                    | "G"
212                    | "Y"
213                    | "J"
214                    | "Seed"
215                    | "PgenCounter"
216                    | "X509Certificate"
217                    | "X509CRL"
218                    | "X509SKI",
219            ) | (
220                Some(XMLENC_NS),
221                "P" | "Q" | "Generator" | "Public" | "seed" | "pgenCounter"
222            ) | (
223                Some(crate::xmldsig::parse::XMLDSIG11_NS),
224                "PublicKey" | "DEREncodedKeyValue" | "X509Digest",
225            )
226        );
227        if binary {
228            let payload = crate::xmldsig::whitespace::XmlBase64Payload::bounded(
229                child,
230                usize::MAX,
231                usize::MAX,
232            )
233            .map_err(|_| invalid("invalid agreement key binary metadata"))?;
234            validate_metadata_len(payload.decoded_len, maximum)?;
235        } else if child.tag_name().namespace() == Some(XMLDSIG_NS)
236            && matches!(
237                child.tag_name().name(),
238                "KeyName" | "X509SubjectName" | "X509IssuerName" | "X509SerialNumber" | "XPath"
239            )
240        {
241            let mut length = 0usize;
242            for text in child
243                .children()
244                .filter(Node::is_text)
245                .filter_map(|node| node.text())
246            {
247                length = length
248                    .checked_add(text.len())
249                    .ok_or_else(|| invalid("agreement metadata length overflow"))?;
250                validate_metadata_len(length, maximum)?;
251            }
252        }
253    }
254    Ok(())
255}
256
257pub(super) fn validate_role_metadata(info: &KeyInfo, maximum: usize) -> Result<(), XmlEncError> {
258    use crate::xmldsig::parse::{KeyInfoSource, KeyValueInfo, RetrievalMethodTransforms};
259    let check = |bytes: &[u8]| validate_metadata_len(bytes.len(), maximum);
260    let check_optional = |bytes: &Option<Vec<u8>>| check(bytes.as_deref().unwrap_or(&[]));
261    for source in &info.sources {
262        match source {
263            KeyInfoSource::KeyName(value) | KeyInfoSource::KeyInfoReference { uri: value } => {
264                check(value.as_bytes())?
265            }
266            KeyInfoSource::DerEncodedKeyValue(value) => check(value)?,
267            KeyInfoSource::KeyValue(value) => match value {
268                KeyValueInfo::Dh {
269                    p,
270                    q,
271                    generator,
272                    public,
273                    seed,
274                    pgen_counter,
275                } => {
276                    for bytes in [p, q, generator, seed, pgen_counter] {
277                        check_optional(bytes)?;
278                    }
279                    check(public)?;
280                }
281                KeyValueInfo::Dsa { p, q, g, y } => {
282                    for bytes in [p, q, g] {
283                        check_optional(bytes)?;
284                    }
285                    check(y)?;
286                }
287                KeyValueInfo::Rsa { modulus, exponent } => {
288                    check(modulus)?;
289                    check(exponent)?;
290                }
291                KeyValueInfo::Ec {
292                    curve_oid,
293                    public_key,
294                } => {
295                    check(curve_oid.as_bytes())?;
296                    check(public_key)?;
297                }
298                KeyValueInfo::Unsupported {
299                    namespace,
300                    local_name,
301                } => {
302                    if let Some(namespace) = namespace {
303                        check(namespace.as_bytes())?;
304                    }
305                    check(local_name.as_bytes())?;
306                }
307                KeyValueInfo::InvalidEcKeyValue => {}
308            },
309            KeyInfoSource::RetrievalMethod {
310                uri,
311                resource_type,
312                transforms,
313            } => {
314                check(uri.as_bytes())?;
315                if let Some(kind) = resource_type {
316                    check(kind.as_bytes())?;
317                }
318                if let RetrievalMethodTransforms::X509DataNodeSetFilter {
319                    expression,
320                    namespaces,
321                } = transforms
322                {
323                    check(expression.as_bytes())?;
324                    for (prefix, uri) in namespaces {
325                        check(prefix.as_bytes())?;
326                        check(uri.as_bytes())?;
327                    }
328                }
329            }
330            KeyInfoSource::X509Data(data) => {
331                for bytes in data.certificates.iter().chain(&data.crls).chain(&data.skis) {
332                    check(bytes)?;
333                }
334                for value in &data.subject_names {
335                    check(value.as_bytes())?;
336                }
337                for (issuer, serial) in &data.issuer_serials {
338                    check(issuer.as_bytes())?;
339                    check(serial.as_bytes())?;
340                }
341                for (algorithm, bytes) in &data.digests {
342                    check(algorithm.as_bytes())?;
343                    check(bytes)?;
344                }
345            }
346        }
347    }
348    Ok(())
349}
350
351pub(super) fn map_key_info_error(error: crate::xmldsig::parse::ParseError) -> XmlEncError {
352    match error {
353        crate::xmldsig::parse::ParseError::Policy(error) => XmlEncError::Policy(error),
354        crate::xmldsig::parse::ParseError::Provider(error) => XmlEncError::Provider(error),
355        error => invalid(&error.to_string()),
356    }
357}
358
359/// Bind an expected agreement descriptor and trusted keys to a content cipher.
360/// Compare XML parameters before provider work; party/KDF substitutions fail.
361pub struct AgreementDecryptor<'a> {
362    expected: &'a AgreementMethod,
363    key: &'a dyn crate::provider::KeyAgreementKey,
364    peer: &'a [u8],
365    purpose: AgreementPurpose,
366}
367
368enum AgreementPurpose {
369    Content(super::DataEncryptionAlgorithm),
370    Wrapping(super::KeyWrapAlgorithm),
371}
372
373impl<'a> AgreementDecryptor<'a> {
374    /// Expectations must come from the application's trusted configuration,
375    /// not be copied from the message as a substitute for party validation.
376    pub fn content(
377        expected: &'a AgreementMethod,
378        key: &'a dyn crate::provider::KeyAgreementKey,
379        peer: &'a [u8],
380        algorithm: super::DataEncryptionAlgorithm,
381    ) -> Self {
382        Self {
383            expected,
384            key,
385            peer,
386            purpose: AgreementPurpose::Content(algorithm),
387        }
388    }
389
390    /// Bind this agreement to a KEK algorithm instead of a content cipher.
391    pub fn wrapping(
392        expected: &'a AgreementMethod,
393        key: &'a dyn crate::provider::KeyAgreementKey,
394        peer: &'a [u8],
395        algorithm: super::KeyWrapAlgorithm,
396    ) -> Self {
397        Self {
398            expected,
399            key,
400            peer,
401            purpose: AgreementPurpose::Wrapping(algorithm),
402        }
403    }
404
405    fn derive(
406        &self,
407        provider: &dyn crate::provider::CryptoProvider,
408        uri: &str,
409        width: usize,
410        policy: &crate::policy::DecryptionPolicy,
411        budget: &mut super::KeyCandidateBudget,
412    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
413        let agreement = KeyAgreementParameters {
414            algorithm: self.expected.algorithm.uri(),
415            peer_public_key: self.peer,
416        };
417        let parameters = self.expected.parameters(uri, width)?;
418        let mut secret =
419            budget.agree_and_derive(provider, policy, self.key, &agreement, &parameters)?;
420        Ok(vec![crate::provider::RecoveredContentKey::confirmed(
421            core::mem::take(&mut *secret),
422        )])
423    }
424}
425
426impl super::DecryptionKeyResolver for AgreementDecryptor<'_> {
427    fn resolve_key_encryption_keys_with_policy(
428        &self,
429        provider: &dyn crate::provider::CryptoProvider,
430        algorithm: super::KeyWrapAlgorithm,
431        source: super::KeyEncryptionKeySource<'_>,
432        policy: &crate::policy::DecryptionPolicy,
433        budget: &mut super::KeyCandidateBudget,
434    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
435        let super::KeyEncryptionKeySource::Agreement(descriptor) = source else {
436            return Err(XmlEncError::KeyNotFound);
437        };
438        if !matches!(self.purpose, AgreementPurpose::Wrapping(expected) if expected == algorithm)
439            || descriptor != self.expected
440        {
441            return Err(XmlEncError::KeyNotFound);
442        }
443        // Reserve the consuming unwrap too, before either expensive primitive.
444        budget.require_available(3)?;
445        self.derive(
446            provider,
447            algorithm.uri(),
448            algorithm.key_len(),
449            policy,
450            budget,
451        )
452    }
453    fn resolve_agreement_content_keys_with_policy(
454        &self,
455        provider: &dyn crate::provider::CryptoProvider,
456        algorithm: super::DataEncryptionAlgorithm,
457        descriptor: &AgreementMethod,
458        policy: &crate::policy::DecryptionPolicy,
459        budget: &mut super::KeyCandidateBudget,
460    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
461        if !matches!(self.purpose, AgreementPurpose::Content(expected) if expected == algorithm)
462            || descriptor != self.expected
463        {
464            return Err(XmlEncError::KeyNotFound);
465        }
466        self.derive(
467            provider,
468            algorithm.uri(),
469            algorithm.key_len(),
470            policy,
471            budget,
472        )
473    }
474
475    fn resolve_key(
476        &self,
477        _provider: &dyn crate::provider::CryptoProvider,
478        _algorithm: super::DataEncryptionAlgorithm,
479        _encrypted_key: Option<&super::EncryptedKey>,
480    ) -> Result<Vec<u8>, XmlEncError> {
481        // Raw key resolution cannot assert that transported agreement metadata
482        // was checked against the application's expected parties and KDF.
483        Err(XmlEncError::KeyNotFound)
484    }
485}