This is a SAML2 library for rust.
This is a work in progress. Pull Requests are welcome.
Current Features:
- Serializing and Deserializing SAML messages
 - IDP-initiated SSO
 - SP-initiated SSO Redirect-POST binding
 - Helpers for validating SAML assertions
- Encrypted assertions aren't supported yet
 
 - Verify SAMLRequest (AuthnRequest) message signatures
 - Create signed SAMLResponse (Response) messages
 
The "xmlsec" feature flag adds basic support for verifying and signing SAML messages. We're using a modified copy of rust-xmlsec library (bindings to xmlsec1 library).
If you want to use the "xmlsec" feature, you'll need to install the following C libs:
- libiconv
 - libtool
 - libxml2
 - libxslt
 - libclang
 - openssl
 - pkg-config
 - xmlsec1
 
We use nix to faciliate reproducible builds of samael.
It will ensure you have the required libraries installed in a way that won't cause any issues with the rest of your system.
If you want to take advantage of this, you'll need to put in a little bit of work.
- Install nix
 - Enable nix flake support
 - Install direnv
 - Install cachix
 - Run 
cachix use nix-communityto enable a binary cache for the rust toolchain (otherwise you'll build the rust toolchain from scratch) - Run 
nix-env -f '<nixpkgs>' -iA nix-direnvandecho "source $HOME/.nix-profile/share/nix-direnv/direnvrc" > $HOME/.direnvrcto improve nix support for direnv cdinto this repo and runnix buildthen, after that finishes,direnv allow- Install the direnv VS Code extension
 
Just run nix build
If you followed the above instructions, just cd-ing into the directory will setup a reproducible dev environment,
but if you don't want to install direnv, then just run nix develop.
From their you can build as normal:
cargo build --features xmlsec
cargo test --features xmlsecHere is some sample code using this library:
use samael::metadata::{ContactPerson, ContactType, EntityDescriptor};
use samael::service_provider::ServiceProviderBuilder;
use std::collections::HashMap;
use std::fs;
use warp::Filter;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    openssl_probe::init_ssl_cert_env_vars();
    let resp = reqwest::get("https://samltest.id/saml/idp")
        .await?
        .text()
        .await?;
    let idp_metadata: EntityDescriptor = samael::metadata::de::from_str(&resp)?;
    let pub_key = openssl::x509::X509::from_pem(&fs::read("./publickey.cer")?)?;
    let private_key = openssl::rsa::Rsa::private_key_from_pem(&fs::read("./privatekey.pem")?)?;
    let sp = ServiceProviderBuilder::default()
        .entity_id("".to_string())
        .key(private_key)
        .certificate(pub_key)
        .allow_idp_initiated(true)
        .contact_person(ContactPerson {
            sur_name: Some("Bob".to_string()),
            contact_type: Some(ContactType::Technical.value().to_string()),
            ..ContactPerson::default()
        })
        .idp_metadata(idp_metadata)
        .acs_url("http://localhost:8080/saml/acs".to_string())
        .slo_url("http://localhost:8080/saml/slo".to_string())
        .build()?;
    let metadata = sp.metadata()?.to_xml()?;
    let metadata_route = warp::get()
        .and(warp::path("metadata"))
        .map(move || metadata.clone());
    let acs_route = warp::post()
        .and(warp::path("acs"))
        .and(warp::body::form())
        .map(move |s: HashMap<String, String>| {
            if let Some(encoded_resp) = s.get("SAMLResponse") {
                let t = sp
                    .parse_response(encoded_resp, &["a_possible_request_id".to_string()])
                    .unwrap();
                return format!("{:?}", t);
            }
            format!("")
        });
    let saml_routes = warp::path("saml").and(acs_route.or(metadata_route));
    warp::serve(saml_routes).run(([127, 0, 0, 1], 8080)).await;
    Ok(())
}