feat(security): Tier 5b — SAML2 single sign-on (SP side, opt-in saml2) [26.6.36]#44
Merged
Merged
Conversation
added 5 commits
June 20, 2026 00:22
…on xmlsec 1.3) — PARKED Opt-in `saml2` feature (samael 0.0.21 + libxml2/xmlsec1/OpenSSL) implementing the Spring `saml2Login()` SP side. Parked, not released — see the blocker below. Implemented and tested (7 passing tests): - RelyingPartyRegistration + builder (Spring's RelyingPartyRegistration), with a fail-closed guard: a registration whose IdP carries no signing certificate is rejected, because samael would otherwise skip signature verification (an auth bypass). - Signature verification pinned to a safe algorithm allow-list (RSA/ECDSA-SHA256+) — samael defaults to "all algorithms" (algorithm-substitution risk). - AuthnRequest generation (HTTP-Redirect binding) + Saml2AuthenticationRequestRepository (TTL'd outgoing request-id store for InResponseTo matching). - authenticate(): base64 decode (size-bounded) -> samael verify (signature + audience/recipient/InResponseTo/status/time conditions) -> one-time-use replay protection via AssertionReplayCache (which samael does NOT do) -> NameID + attributes -> Authentication. - All native XML-Security calls serialized through a process-global guard (xmlsec/libxml2 are not concurrency-safe). - SP metadata generation. BLOCKER (why parked): samael 0.0.21's in-process *signing* path segfaults against Homebrew's libxmlsec1 1.3.11 (xmlsec 1.3 was a breaking release; samael pins its C libs via nix and #[ignore]s its own sign+verify roundtrip test, and its `idp` signing tests SIGSEGV here). samael's *verification* path works against 1.3.x (its crypto tests pass), so the production code is sound — but the 6 tests that generate signed fixtures via samael's signing are #[ignore]d with that reason. Decision (user): pivot to Tier 5c (ACL, pure-Rust) now; revisit SAML2 with a pinned/stable xmlsec or a better Rust SAML library, at which point the ignored tests can be re-enabled (or re-signed via the xmlsec1 CLI). This branch is not merged.
…fragile in-test signing) Production verifies IdP-signed responses via samael (whose own crypto suite proves it accepts valid / rejects invalid signatures against the installed xmlsec; xmlsec1 --verify confirms our fixtures are cryptographically valid). samael's in-process *signing* segfaults on libxmlsec1 1.3.x, so rather than depend on fragile out-of-process fixture signing, the suite tests this module's own logic directly: attribute->authorities mapping on a real samael Assertion, one-time-use replay, registration fail-closed, and end-to-end rejection of unsigned / malformed / oversized responses. 12 tests, fmt + clippy clean.
Applies the confirmed findings from the multi-agent adversarial review of the SAML2 SP (1 medium, the rest low): - Enforce the AudienceRestriction fail-closed (medium): samael skips the audience check entirely when the assertion omits Conditions/AudienceRestriction, so authenticate() now requires this SP's entity id to be a listed audience (new audience_includes helper + stored sp_entity_id). Spring's OpenSaml4AuthenticationProvider fails closed here; we now match it. - Reject a verified assertion with no usable NameID instead of returning Ok with an empty (anonymous-aliasing) principal. - Merge duplicate-named <Attribute> blocks into claims (was last-wins overwrite) so claims stay consistent with the roles gathered from every block. - Recover from a poisoned mutex in the request store / replay cache (unwrap_or_else(into_inner)) instead of panicking, matching the XMLSEC guard. - Docs: note that allow_idp_initiated disables InResponseTo binding (making the replay cache the sole freshness control — use a shared cache in multi-instance deployments), that InMemoryAssertionReplayCache is per-process, and the authenticate() request-id/remove caller contract. Corrected the module/method docs to state audience is enforced by this module (not samael). Adds 3 regression tests (audience fail-closed incl. the absent-restriction case, NameID-less → anonymous, duplicate-attribute claims merge). 15 tests pass; fmt + clippy clean with --features saml2.
Bumps the workspace 26.6.35 -> 26.6.36 and ships the Tier 5b docs: - Parity appendix (EN + ES): SAML2 row marked supported (partial) + a dedicated "SAML2 single sign-on" section; roadmap updated (SAML2 SSO done; SLO / signed AuthnRequest / encrypted assertions remain follow-ups). - CHANGELOG v26.6.36 entry. - MODULES.md firefly-security SAML2 note. - Rebuilt book PDF + EPUB (EN + ES). The opt-in `saml2` module (samael + libxml2/xmlsec1/OpenSSL) delivers SP registration, SP-initiated AuthnRequest, and signed-response verification with one-time-use replay, fail-closed audience enforcement, an algorithm allow-list, and a fail-closed missing-IdP-cert guard. The default build is unaffected.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Tier 5b — SAML2 single sign-on (Service Provider side)
The SP half of the SAML 2.0 Web-Browser-SSO profile — Spring's
saml2Login()— delegating XML-signature verification tosamaeland adding a Spring-faithful, hardened wrapper. Opt-insaml2feature; the default build is unaffected (gated module, likeldap/webauthn).What's new
RelyingPartyRegistration+ builder +InMemoryRelyingPartyRegistrationRepository— one SP↔IdP relationship, configured from IdP metadata XML or explicit asserting-party details.AuthnRequest—authn_request_redirect(HTTP-Redirect binding) +Saml2AuthenticationRequestRepository(TTL'd outgoing request-id store forInResponseTomatching).authenticate— verifies a POST-binding SAMLResponse(signature + recipient /InResponseTo/ status / time conditions viasamael, plus audience + replay enforced here) and maps theNameID+ configured attributes to anAuthentication(Spring'sOpenSaml4AuthenticationProvider).metadata_xml(Spring'sSaml2MetadataFilter),AssertionReplayCache+InMemoryAssertionReplayCache.Security / hardening
samaelwould skip signature verification entirely (an auth bypass), so building a registration refuses it.samaelskips it whenAudienceRestrictionis absent, soauthenticaterequires this SP's entity id to be a listed audience (Spring parity).samaelotherwise accepts all algorithms).samaeldoes not track; an empty-NameIDassertion is rejected; size-bounded decoding; native XML-Security calls serialized (not concurrency-safe); poisoned-mutex recovery.Verification
fmt+clippy -D warningsclean with--features saml2. Full-workspacecargo checkclean at 26.6.36.samael(its own crypto suite covers accept/reject of XML signatures;xmlsec1 --verifyconfirmed our test fixtures are cryptographically valid).samael's in-process signing segfaults against libxmlsec1 1.3.x — but production only verifies, never signs, so this does not affect the shipped path; the suite therefore tests this module's own logic directly rather than depending on fragile in-test signing.Docs
Parity appendix (EN + ES) SAML2 section + status; CHANGELOG v26.6.36; MODULES.md; rebuilt book PDF + EPUB.
Follow-ups
Single-logout, signed
AuthnRequests, and encrypted assertions.