xdoc-rs 0.1.1

Declarative XML engine for Rust
Documentation
use crate::core::{ErrorKind, XmlError, XmlResult};

pub const XMLDSIG_ENVELOPED_SIGNATURE_URI: &str =
    "http://www.w3.org/2000/09/xmldsig#enveloped-signature";
pub const XMLDSIG_SIGNED_PROPERTIES_TYPE_URI: &str = "http://uri.etsi.org/01903#SignedProperties";

const C14N_11_URI: &str = "http://www.w3.org/2006/12/xml-c14n11";
const C14N_10_URI: &str = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315";
const EXCLUSIVE_C14N_10_URI: &str = "http://www.w3.org/2001/10/xml-exc-c14n#";
const SHA1_URI: &str = "http://www.w3.org/2000/09/xmldsig#sha1";
const SHA256_URI: &str = "http://www.w3.org/2001/04/xmlenc#sha256";
const SHA512_URI: &str = "http://www.w3.org/2001/04/xmlenc#sha512";
const RSA_SHA1_URI: &str = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
const RSA_SHA256_URI: &str = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";

/// Canonicalization algorithms recognized by the signature module.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum CanonicalizationAlgorithm {
    #[default]
    CanonicalXml11,
    CanonicalXml10,
    ExclusiveXml10,
}

impl CanonicalizationAlgorithm {
    pub fn uri(self) -> &'static str {
        match self {
            Self::CanonicalXml11 => C14N_11_URI,
            Self::CanonicalXml10 => C14N_10_URI,
            Self::ExclusiveXml10 => EXCLUSIVE_C14N_10_URI,
        }
    }

    pub fn from_uri(uri: &str) -> XmlResult<Self> {
        match uri {
            C14N_11_URI => Ok(Self::CanonicalXml11),
            C14N_10_URI => Ok(Self::CanonicalXml10),
            EXCLUSIVE_C14N_10_URI => Ok(Self::ExclusiveXml10),
            _ => Err(unsupported_algorithm("canonicalization", uri)),
        }
    }
}

/// Digest algorithms recognized by XMLDSig/XAdES.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum DigestAlgorithm {
    /// Legacy SHA-1 URI support for parsing and explicit rejection.
    Sha1,
    #[default]
    Sha256,
    Sha512,
}

impl DigestAlgorithm {
    pub fn uri(self) -> &'static str {
        match self {
            Self::Sha1 => SHA1_URI,
            Self::Sha256 => SHA256_URI,
            Self::Sha512 => SHA512_URI,
        }
    }

    pub fn from_uri(uri: &str) -> XmlResult<Self> {
        match uri {
            SHA1_URI => Ok(Self::Sha1),
            SHA256_URI => Ok(Self::Sha256),
            SHA512_URI => Ok(Self::Sha512),
            _ => Err(unsupported_algorithm("digest", uri)),
        }
    }

    pub fn ensure_allowed_for_generation(self) -> XmlResult<()> {
        match self {
            Self::Sha1 => Err(XmlError::new(
                ErrorKind::Signature,
                "SHA-1 is not allowed for signature generation",
            )),
            Self::Sha256 | Self::Sha512 => Ok(()),
        }
    }
}

/// Signature algorithms recognized by XMLDSig/XAdES.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum SignatureAlgorithm {
    /// Legacy RSA-SHA1 URI support for parsing and explicit rejection.
    RsaSha1,
    #[default]
    RsaSha256,
}

impl SignatureAlgorithm {
    pub fn uri(self) -> &'static str {
        match self {
            Self::RsaSha1 => RSA_SHA1_URI,
            Self::RsaSha256 => RSA_SHA256_URI,
        }
    }

    pub fn from_uri(uri: &str) -> XmlResult<Self> {
        match uri {
            RSA_SHA1_URI => Ok(Self::RsaSha1),
            RSA_SHA256_URI => Ok(Self::RsaSha256),
            _ => Err(unsupported_algorithm("signature", uri)),
        }
    }

    pub fn ensure_allowed_for_generation(self) -> XmlResult<()> {
        match self {
            Self::RsaSha1 => Err(XmlError::new(
                ErrorKind::Signature,
                "RSA-SHA1 is not allowed for signature generation",
            )),
            Self::RsaSha256 => Ok(()),
        }
    }
}

fn unsupported_algorithm(kind: &str, uri: &str) -> XmlError {
    XmlError::new(
        ErrorKind::Signature,
        format!("unsupported {kind} algorithm URI `{uri}`"),
    )
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn signature_algorithms_expose_xml_uris() {
        assert_eq!(
            CanonicalizationAlgorithm::CanonicalXml11.uri(),
            "http://www.w3.org/2006/12/xml-c14n11"
        );
        assert_eq!(
            CanonicalizationAlgorithm::CanonicalXml10.uri(),
            "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"
        );
        assert_eq!(
            DigestAlgorithm::Sha256.uri(),
            "http://www.w3.org/2001/04/xmlenc#sha256"
        );
        assert_eq!(
            DigestAlgorithm::Sha512.uri(),
            "http://www.w3.org/2001/04/xmlenc#sha512"
        );
        assert_eq!(
            SignatureAlgorithm::RsaSha256.uri(),
            "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
        );
    }

    #[test]
    fn signature_algorithms_parse_known_uris() {
        assert_eq!(
            CanonicalizationAlgorithm::from_uri("http://www.w3.org/2006/12/xml-c14n11")
                .expect("c14n uri"),
            CanonicalizationAlgorithm::CanonicalXml11
        );
        assert_eq!(
            CanonicalizationAlgorithm::from_uri("http://www.w3.org/TR/2001/REC-xml-c14n-20010315")
                .expect("c14n 1.0 uri"),
            CanonicalizationAlgorithm::CanonicalXml10
        );
        assert_eq!(
            DigestAlgorithm::from_uri("http://www.w3.org/2001/04/xmlenc#sha256")
                .expect("digest uri"),
            DigestAlgorithm::Sha256
        );
        assert_eq!(
            DigestAlgorithm::from_uri("http://www.w3.org/2001/04/xmlenc#sha512")
                .expect("digest uri"),
            DigestAlgorithm::Sha512
        );
        assert_eq!(
            SignatureAlgorithm::from_uri("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256")
                .expect("signature uri"),
            SignatureAlgorithm::RsaSha256
        );
    }

    #[test]
    fn signature_algorithms_reject_unknown_uris() {
        let error = DigestAlgorithm::from_uri("urn:unknown")
            .expect_err("unknown digest algorithm must fail");

        assert_eq!(error.kind(), &ErrorKind::Signature);
        assert!(error.message().contains("unsupported digest algorithm"));
    }

    #[test]
    fn signature_algorithms_reject_sha1_for_generation() {
        let digest_error = DigestAlgorithm::Sha1
            .ensure_allowed_for_generation()
            .expect_err("sha1 digest must be rejected");
        let signature_error = SignatureAlgorithm::RsaSha1
            .ensure_allowed_for_generation()
            .expect_err("rsa-sha1 signature must be rejected");

        assert_eq!(digest_error.kind(), &ErrorKind::Signature);
        assert_eq!(signature_error.kind(), &ErrorKind::Signature);
    }
}