opensaml-rs
Pure-Rust SAML 2.0 — Service Provider and Identity Provider in one crate. No libxml2, no xmlsec1, no OpenSSL build chain for the protocol layer. XML cryptography (XML-DSig, XML-Enc, C14N) is delegated to bergshamra behind an optional feature that is on by default.
Behavior is ported to parity with npm samlify v2.10.2, with the upstream test suite carried over 1:1 and additional hardening (audience restriction, InResponseTo binding, XML Signature Wrapping tests, metadata key pinning, signed-metadata verification).
[]
= "0.1"
# Crypto-free protocol layer only:
# opensaml = { version = "0.1", default-features = false }
The workspace crate
samlifyis onlypub use opensaml::*;— a familiar Rust name, not the npm package.
Why opensaml vs. samael?
| opensaml | samael | |
|---|---|---|
Native C/XML stack (libxml2, xmlsec1, libxslt, …) |
None — protocol in Rust; crypto via bergshamra |
Required with default xmlsec feature |
| OpenSSL at build time | No | Yes (typical xmlsec setup) |
| System packages to install | None | libiconv, libtool, libxml2, libxslt, libclang, openssl, pkg-config, xmlsec1, … |
| Cross-compile (musl, distroless, arm64) | Straightforward | Native libs needed on every target |
cargo audit for the SAML stack |
Rust dependency graph | Split: crates + OS packages |
| SP + IdP in one library | First-class ServiceProvider + IdentityProvider |
SP-oriented; IdP-initiated SSO supported |
| HTTP bindings | POST, Redirect, POST-SimpleSign | Redirect → POST (primary documented path) |
| Single Logout | Create + parse, all three bindings | Limited / evolving |
| Metadata | Parse + generate (SP and IdP) | Deserialize-focused |
| Signed metadata verification | Yes | — |
| XSW / reference hardening | Explicit guard + strict verification via bergshamra | Inherits xmlsec behavior |
#![forbid(unsafe_code)] |
Yes | No |
| API shape | Ported from npm samlify v2.10.2 |
Builder-style ServiceProvider |
Takeaway: samael is the established SAML crate on crates.io, but signing and verification pull in libxml2 + xmlsec1 + OpenSSL and a host of system libraries. opensaml keeps the protocol layer and XML crypto on a Rust-only path (no C XML stack), ships SP and IdP with three bindings and Single Logout, and adds hardening beyond the samlify port (audience, InResponseTo, XSW tests, metadata key pinning).
What you can do
| Area | Highlights |
|---|---|
| Web SSO | Signed AuthnRequest / Response, HTTP-POST and HTTP-Redirect, POST-SimpleSign |
| Metadata | Parse peer metadata, generate SP/IdP descriptors, verify signed aggregates |
| Single Logout | LogoutRequest / LogoutResponse create and parse (all three bindings) |
| Validation | Issuer, <Audience>, assertion time window, status codes, optional InResponseTo |
| Crypto (default) | XML-DSig sign/verify, XML-Enc encrypt/decrypt, detached redirect signatures, anti-wrapping |
| Extraction | quick-xml DOM + local-name field sets (samlify-compatible extract engine) |
Quick start
Service Provider — login request
use Binding;
use EntitySetting;
use ;
use ServiceProvider;
let sp = from_config?;
// Binding::Redirect for DEFLATE + query-string dispatch
let request = sp.create_login_request?;
// POST: request.context is the base64 SAMLRequest
// Redirect: use binding helpers to build the redirect URL
Identity Provider — login response
use Binding;
use ;
use HttpRequest;
use LoginResponseOptions;
use IdentityProvider;
let req = post;
let parsed = idp.parse_login_request?;
let response = idp.create_login_response?;
Service Provider — consume response
use HttpRequest;
let resp = post;
// Prefer binding to the outbound AuthnRequest id (replay / CSRF hygiene)
let result = sp.parse_login_response_with_request_id?;
let name_id = result.extract.get_str;
Metadata
use Binding;
use ;
// Parse peer IdP metadata
let idp_meta = from_xml?;
let sso_url = idp_meta.get_single_sign_on_service;
// Generate your SP descriptor
let xml = generate_sp_metadata;
Single Logout
use Binding;
use ;
use HttpRequest;
use ;
let logout = create_logout_request?;
let resp = post;
let parsed = parse_logout_response?;
End-to-end example (signed round-trip)
A runnable SP → IdP → SP flow with RSA-SHA256 signatures:
Source: crates/opensaml/examples/sso.rs.
Crates in this workspace
| Crate | Role |
|---|---|
opensaml |
Library: constants, XML, templates, metadata, bindings, ServiceProvider / IdentityProvider, flow, logout, validation, crypto (feature). |
samlify |
Thin re-export: pub use opensaml::*; — drop-in crate name only. |
Crate-level API details: docs.rs/opensaml and the opensaml crate rustdoc module map.
Features
[]
= ["crypto-bergshamra"]
= ["dep:bergshamra"] # XML-DSig, XML-Enc, detached signatures
With default-features = false, the protocol layer still builds messages, parses metadata, and runs extraction; any operation that needs signing, verification, or encryption returns OpenSamlError::Unsupported (fail closed).
With crypto-bergshamra enabled (default):
- Verification can pin to metadata-declared keys (
trusted_keys_only). - Strict signed-reference placement helps mitigate XML Signature Wrapping (XSW).
- An additional wrapping guard runs in
crypto::verify.
Security
#![forbid(unsafe_code)]on the crate root.- Inbound responses: signature (when required), issuer,
<Audience>, assertion validity window, SAML status; optionalInResponseToviaparse_login_response_with_request_id. - DOCTYPE / XXE rejection in the XML layer; optional XSD validation via
context::set_schema_validator. - Pre-1.0 and not externally audited — review crypto and deployment choices before production.
Status
| Version | 0.1.1 (APIs may change until 1.0) |
| Reference port | npm samlify v2.10.2 |
| Tests | Upstream samlify suite ported 1:1 + XSW / audience / metadata-signature cases |
| Audit | None yet |
Development
Upstream reference sources (gitignored): ./scripts/fetch-upstream-samlify.sh → reference/upstream-samlify/2.10.2/.
License
MIT.