Pure-Rust SAML 2.0 Service Provider and Identity Provider support. The
protocol layer uses Rust XML parsing and does not require libxml2, xmlsec1,
or an OpenSSL build chain. XML cryptography (XML-DSig, XML-Enc, C14N, detached
message signatures) is delegated to bergshamra.
The default crypto-bergshamra compatibility feature selects RustCrypto and
preserves the historical optional algorithm and PKCS#11 capabilities.
[dependencies]
saml-rs = "0.1"
# Crypto-free protocol layer only:
# saml-rs = { version = "0.1", default-features = false }The project now publishes one crate: saml-rs. The Rust import path is
saml_rs.
saml-rs is currently pre-1.0, so minor releases may contain breaking API or
behavior changes. Before updating across minor versions, review the
migration guides.
saml-rs is aimed at applications that need SAML SP/IdP flows without a C XML
security stack in their build and deployment environment.
| Area | saml-rs |
|---|---|
| Native dependencies | No libxml2, xmlsec1, or OpenSSL build chain for the protocol layer |
| Roles | Service Provider and Identity Provider |
| Bindings | HTTP-POST, HTTP-Redirect, HTTP-POST-SimpleSign |
| Metadata | Parse and generate SP/IdP metadata; verify signed metadata |
| Single Logout | Create and parse LogoutRequest / LogoutResponse |
| Crypto | XML-DSig, XML-Enc, detached signatures via bergshamra |
| Hardening | Request correlation, audience/destination/issuer checks, XSW guards, bounded parsing |
| Unsafe code | #![forbid(unsafe_code)] |
Compared with samael, the main tradeoff is
deployment shape: samael is the established Rust SAML crate and commonly uses
the native xmlsec stack, while saml-rs keeps the SAML protocol path
Rust-only and delegates XML crypto to a Rust crate.
| Area | Highlights |
|---|---|
| Web SSO | Signed AuthnRequest / Response, HTTP-POST, HTTP-Redirect, POST-SimpleSign |
| Metadata | Parse peer metadata, generate SP/IdP descriptors, verify signed aggregates |
| Single Logout | Create and parse logout request/response flows across all three bindings |
| Validation | Issuer, audience, destination/recipient, bearer confirmation, status, time windows, request correlation |
| Crypto | XML-DSig sign/verify, XML-Enc encrypt/decrypt, detached message signatures, metadata key pinning |
| Extraction | quick-xml DOM plus local-name field extraction |
The high-level Saml API currently focuses on browser Web SSO, metadata-driven
SP/IdP setup, XML signature/encryption through bergshamra, and Single Logout.
It does not yet implement Artifact resolution, SOAP/back-channel profiles,
ECP/PAOS, SAML query protocols, NameID management, or metadata federation. If
you need one of those profiles for a real interoperability target, please open
an issue with the profile, binding, IdP/SP product, and a minimal expected flow
so we can consider the implementation.
The primary API is the typed Saml facade. Build local SP/IdP configuration
with SpConfig::builder and IdpConfig::builder, import peer metadata into
typed descriptors, and keep the returned Pending<_> value with your browser
session while the SAML round trip is in flight.
A signed SP -> IdP -> SP round trip is available as an executable example:
cargo run -p saml-rs --example ssoSource: examples/sso.rs.
The repository also includes a typed Single Logout walkthrough in
examples/slo.rs and a low-level compatibility walkthrough
in examples/raw_compat.rs.
The crate-root docs contain
doctested fragments for the typed Saml facade, including
Saml<Sp>::start_sso,
Saml<Sp>::finish_sso,
Saml<Idp>::receive_sso,
and Saml<Sp>::finish_slo.
Those rustdoc snippets are compiled by cargo test --doc; the README stays as
an entry point and links to the complete examples above.
- Build local SP state with
SpConfig::builderandSaml::sp. - Import peer IdP metadata into
IdpDescriptor. - Start SSO with
sp.start_sso(...)and storestarted.pendingwith the browser session. - In the ACS handler, pass the posted response fields and the matching pending
value to
sp.finish_sso(...).
See examples/sso.rs for a complete signed SP -> IdP -> SP
round trip and the doctested crate-root SSO
fragment for the
compact API shape.
The IdP side mirrors the SP flow: import peer SP metadata into SpDescriptor,
parse an AuthnRequest with idp.receive_sso(...), then produce a typed
browser response with idp.respond_sso(...). The complete path is exercised in
examples/sso.rs, and the short rustdoc version is in the
Identity Provider flows
crate-root section.
IdpConfig::builder(...).issuance_lifetime(Duration) controls the shared
issuance window for typed IdP output. One captured UTC IssueInstant derives both
SSO Conditions@NotOnOrAfter and bearer
SubjectConfirmationData@NotOnOrAfter. The default is exactly five minutes;
that duration is saml-rs policy, not an OASIS requirement.
Typed Single Logout starts from session.logout_subject(), stores the
PendingLogoutRequest, and finishes only with the matching LogoutResponse.
Peer-initiated logout uses Received<LogoutRequest> rather than free-form
request ID strings. See examples/slo.rs for the complete
typed walkthrough and the doctested SLO
fragment for the compact
shape.
Saml<Idp>::start_slo models the local IdP as the SAML Session Authority and
always emits a UTC LogoutRequest@NotOnOrAfter derived from the same
IdpConfig::issuance_lifetime and captured IssueInstant. The exact values
are persisted in PendingLogoutRequest. Custom typed IdP LogoutRequest
templates must place NotOnOrAfter="{NotOnOrAfter}" as one unqualified root
attribute so the library can validate and sign the final value. Typed
Saml<Sp>::start_slo does not synthesize this role-specific attribute.
Inbound LogoutRequest messages require a UTC IssueInstant; saml-rs does not
invent a maximum age for it. Generic inbound NotOnOrAfter remains optional
under the protocol schema and is not rejected merely because a
Session-Authority producer rule would require it on a narrower outbound flow.
When present it must be UTC, and saml-rs rejects the request at its effective exclusive
deadline. That fail-closed rejection is a library policy permitted by SAML,
not an OASIS receiver MUST. ClockSkew controls the NotOnOrAfter tolerance,
and replay storage uses the same skew-adjusted deadline instead of generic
retention when the attribute is present.
Metadata trust is explicit. The rustdoc
Metadata trust
section describes production-shaped signed metadata validation with pinned
certificates. MetadataTrustPolicy::UnsignedForCompatibility is available for
legacy interoperability, but it is a compatibility exception rather than a
production default.
The compact rustdoc flow snippets use
ReplayPolicy::DisabledForCompatibility only to keep examples dependency-free.
Production inbound validation should use ReplayPolicy::RequireCache with a
caller-owned replay cache and the retention guidance in
SamlValidationContext.
The low-level compatibility API remains available under saml_rs::raw for
callers that need direct access to ServiceProvider, IdentityProvider,
HttpRequest, BindingContext, or protocol helper functions. New browser
SSO/SLO integrations should start with Saml, typed descriptors, and the
builder-backed config types shown above.
Public raw create_logout_request* helpers retain their compatibility output:
they do not synthesize NotOnOrAfter, and the public
LOGOUT_REQUEST_TEMPLATE remains unchanged.
Use visible docs.rs modules, crate-root re-exports, and saml_rs::raw before
reaching for hidden lower-level module paths.
[features]
default = ["crypto-bergshamra"]
crypto-bergshamra = [
"crypto-rustcrypto",
"crypto-legacy-algorithms",
"crypto-post-quantum",
"crypto-pkcs11",
]
crypto-rustcrypto = ["dep:bergshamra", "bergshamra/rustcrypto"]
crypto-aws-lc = ["dep:bergshamra", "bergshamra/aws-lc"]
crypto-fips = ["dep:bergshamra", "bergshamra/fips"]With default-features = false, the protocol layer still builds messages,
parses metadata, and runs extraction. Operations that need signing,
verification, or encryption return SamlError::Unsupported.
All published workspace packages require Rust 1.88. Select at most one of
crypto-rustcrypto, crypto-aws-lc, and crypto-fips; provider combinations
are rejected at compile time. Disable default features before selecting AWS-LC
or FIPS. The compatibility packages forward the same feature names.
crypto-legacy-algorithms, crypto-post-quantum, and crypto-pkcs11 forward
those Bergshamra capabilities without selecting a provider. The default
crypto-bergshamra alias enables them with RustCrypto to preserve existing
behavior; direct provider selection starts with only that provider's baseline.
Bergshamra supports AWS-LC and FIPS on Linux x86_64/aarch64. The saml-rs
provider matrix currently validates Linux x86_64; Linux aarch64 is an upstream
capability that this repository does not exercise in CI. saml-rs initializes
Bergshamra before its first crypto operation. Applications can fail early and
inspect the result during startup:
use saml_rs::{initialize_crypto_provider, CryptoFipsStatus};
let provider = initialize_crypto_provider()?;
assert_ne!(provider.fips_status(), CryptoFipsStatus::Uninitialized);
# Ok::<(), saml_rs::SamlError>(())Initialization, attestation, unsupported-algorithm, and key-import failures are
fail-closed and map to SamlError::Crypto. crypto-fips means that the selected
AWS-LC provider actively attested FIPS mode; it does not claim that a consuming
binary or deployment is FIPS certified. FIPS policy rejects algorithms outside
its approved set, including the currently exposed SHA-1-based
RSA_OAEP_MGF1P XML-Enc key transport. Bergshamra's AWS-LC providers reject
signing with RSA_SHA1. The FIPS provider also rejects verifying RSA_SHA1;
non-FIPS AWS-LC still verifies inbound RSA-SHA1 Redirect and XML-DSig
signatures. AWS-LC has a narrower capability set than RustCrypto; consult
Bergshamra's
provider-capability documentation
before enabling custom algorithm URIs.
With crypto-bergshamra enabled:
- XML signatures can be verified against metadata-declared keys.
- Signed-reference placement checks help mitigate XML Signature Wrapping (XSW).
- XML-Enc support is available. On the default RustCrypto provider, software
RSA key-transport decryption is gated off by default and requires an
explicit compatibility opt-in through
XmlEncryptionPolicy. AWS-LC decrypts RSA-OAEP with the default options.
saml-rs is pre-1.0 and has not had an external security audit. Review the
crate, configuration, and peer metadata trust model before production use.
Security-sensitive defaults and checks include:
#![forbid(unsafe_code)]on the crate root.- DOCTYPE / XXE rejection and bounded XML parsing before authentication.
- XML escaping for generated templates, metadata endpoint locations, and SAML attribute values.
- Response validation for issuer, SAML status, assertion time window, audience,
destination/recipient, bearer subject confirmation, and
InResponseTo. - Logout validation for issuer and request/response correlation.
- Signed metadata verification with root coverage requirements.
- AuthnRequest root-signature coverage when signed requests are required.
- Detached Redirect/SimpleSign signatures bound to the fields consumed by the flow parser.
- HTTP-Redirect raw DEFLATE output limits.
- XML-Enc software RSA key-transport decryption disabled by default on
RustCrypto because that backend, reached through
bergshamra/kryptering, is affected by RUSTSEC-2023-0071. AWS-LC and FIPS do not apply this gate.
Schema validation is optional defense in depth via
context::set_schema_validator.
cargo fmt --all --check
cargo clippy -p saml-rs --all-targets -- -D warnings
cargo nextest run -p saml-rs
cargo test -p saml-rs --doc
RUSTDOCFLAGS="-D warnings -D missing_docs" cargo doc -p saml-rs --lib --no-deps
cargo test -p saml-rs --doc --no-default-features
cargo check -p saml-rs --no-default-features
cargo nextest run -p saml-rs --no-default-features --features crypto-rustcrypto
# AWS-LC/FIPS checks run on supported Linux runners; see .github/workflows/ci.yml.MIT.