samael 0.0.21

A SAML2 library for Rust
use quick_xml::events::Event;

use crate::crypto::{decode_x509_cert, CertificateDer, Crypto, CryptoProvider};
use crate::schema::AuthnRequest;

use super::error::Error;

pub struct UnverifiedAuthnRequest<'a> {
    pub request: AuthnRequest,
    xml: &'a str,
}

impl<'a> UnverifiedAuthnRequest<'a> {
    pub fn from_xml(xml: &str) -> Result<UnverifiedAuthnRequest, Error> {
        Ok(UnverifiedAuthnRequest {
            request: xml.parse()?,
            xml,
        })
    }

    fn get_certs_der(&self) -> Result<Vec<CertificateDer>, Error> {
        let x509_certs = self
            .request
            .signature
            .as_ref()
            .ok_or(Error::NoSignature)?
            .key_info
            .as_ref()
            .map(|ki| ki.iter())
            .ok_or(Error::NoKeyInfo)?
            .flat_map(|d| d.x509_data.as_ref())
            .flat_map(|d| d.certificates.iter())
            .map(|cert| decode_x509_cert(cert))
            .collect::<Result<Vec<_>, _>>()
            .map_err(|_| Error::InvalidCertificateEncoding)?;

        if x509_certs.is_empty() {
            return Err(Error::NoCertificate);
        }

        Ok(x509_certs)
    }

    pub fn try_verify_self_signed(self) -> Result<VerifiedAuthnRequest, Error> {
        let xml = self.xml.as_bytes();
        self.get_certs_der()?
            .into_iter()
            .map(|der_cert| Ok(Crypto::verify_signed_xml(xml, &der_cert, Some("ID"))?))
            .reduce(|a, b| a.or(b))
            .ok_or(Error::UnexpectedError)?
            .map(|()| VerifiedAuthnRequest(self.request))
    }

    pub fn try_verify_with_cert(
        self,
        der_cert: &CertificateDer,
    ) -> Result<VerifiedAuthnRequest, Error> {
        Crypto::verify_signed_xml(self.xml.as_bytes(), der_cert, Some("ID"))?;
        Ok(VerifiedAuthnRequest(self.request))
    }
}

pub struct VerifiedAuthnRequest(AuthnRequest);

impl std::ops::Deref for VerifiedAuthnRequest {
    type Target = AuthnRequest;
    fn deref(&self) -> &AuthnRequest {
        &self.0
    }
}

impl TryFrom<VerifiedAuthnRequest> for Event<'_> {
    type Error = Box<dyn std::error::Error>;

    fn try_from(value: VerifiedAuthnRequest) -> Result<Self, Self::Error> {
        (&value).try_into()
    }
}

impl TryFrom<&VerifiedAuthnRequest> for Event<'_> {
    type Error = Box<dyn std::error::Error>;

    fn try_from(value: &VerifiedAuthnRequest) -> Result<Self, Self::Error> {
        value.0.clone().try_into()
    }
}

#[cfg(test)]
mod test {
    use super::UnverifiedAuthnRequest;
    use crate::traits::ToXml;

    #[test]
    fn test_request_deserialize_and_serialize() {
        let authn_request_xml = include_str!("../../test_vectors/authn_request.xml");
        let unverified =
            UnverifiedAuthnRequest::from_xml(authn_request_xml).expect("failed to parse");
        let expected_verified = unverified
            .try_verify_self_signed()
            .expect("failed to verify self signed signature");
        let verified_request_xml = expected_verified
            .to_string()
            .expect("Failed to serialize verified authn request");
        let reparsed_unverified =
            UnverifiedAuthnRequest::from_xml(&verified_request_xml).expect("failed to parse");
        assert_eq!(reparsed_unverified.request, expected_verified.0);
    }
}