Skip to main content

alibi_plugins/sso/
encryption_algorithms.rs

1//! Application policies for SAML assertion key transport and data encryption.
2use super::SsoDeprecatedAlgorithmBehavior;
3#[derive(Clone, Debug, Default)]
4pub struct SsoSamlEncryptionPolicy {
5    pub on_deprecated: SsoDeprecatedAlgorithmBehavior,
6    pub allowed_key_encryption_algorithms: Option<Vec<String>>,
7    pub allowed_data_encryption_algorithms: Option<Vec<String>>,
8}
9
10use super::saml_crypto::Failure;
11use bergshamra::xml::{Document, NodeId};
12const XMLENC: &str = "http://www.w3.org/2001/04/xmlenc#";
13fn algorithm(doc: &Document<'_>, root: NodeId, parent: &str) -> Option<String> {
14    let parent = doc.descendants(root).into_iter().find(|node| {
15        doc.element(*node)
16            .is_some_and(|element| element.name.matches(Some(XMLENC), parent))
17    })?;
18    let method = doc.children(parent).into_iter().find(|node| {
19        doc.element(*node)
20            .is_some_and(|element| element.name.matches(Some(XMLENC), "EncryptionMethod"))
21    })?;
22    doc.get_attribute(method, "Algorithm")
23        .filter(|value| !value.is_empty())
24        .map(str::to_owned)
25}
26pub(super) fn validate(xml: &str, policy: &SsoSamlEncryptionPolicy) -> Result<(), Failure> {
27    let Ok(doc) = bergshamra::xml::uppsala::parse(xml) else {
28        return Ok(());
29    };
30    let Some(root) = doc.document_element() else {
31        return Ok(());
32    };
33    for (value, label, allowed, deprecated, recommendation) in [
34        (
35            algorithm(&doc, root, "EncryptedKey"),
36            "key",
37            &policy.allowed_key_encryption_algorithms,
38            "http://www.w3.org/2001/04/xmlenc#rsa-1_5",
39            "RSA-OAEP",
40        ),
41        (
42            algorithm(&doc, root, "EncryptedData"),
43            "data",
44            &policy.allowed_data_encryption_algorithms,
45            "http://www.w3.org/2001/04/xmlenc#tripledes-cbc",
46            "AES-GCM",
47        ),
48    ] {
49        let Some(value) = value else { continue };
50        if let Some(allowed) = allowed {
51            if !allowed.contains(&value) {
52                return Err(Failure {
53                    code: "saml_algorithm_not_allowed",
54                    message: format!(
55                        "SAML {label} encryption algorithm not in allow-list: {value}"
56                    ),
57                });
58            }
59        } else if value == deprecated {
60            let message = format!(
61                "SAML response uses deprecated {label} encryption algorithm: {value}. Please configure your IdP to use {recommendation}."
62            );
63            match policy.on_deprecated {
64                SsoDeprecatedAlgorithmBehavior::Reject => {
65                    return Err(Failure {
66                        code: "saml_deprecated_algorithm",
67                        message,
68                    });
69                }
70                SsoDeprecatedAlgorithmBehavior::Warn => tracing::warn!("{message}"),
71                SsoDeprecatedAlgorithmBehavior::Allow => {}
72            }
73        }
74    }
75    Ok(())
76}