Skip to content

fix(saml): bump crystal-saml — namespaces + Shibboleth signature verification - #34

Merged
camreeves merged 1 commit into
masterfrom
fix/saml-xmlns-shard-bump
Aug 17, 2026
Merged

fix(saml): bump crystal-saml — namespaces + Shibboleth signature verification#34
camreeves merged 1 commit into
masterfrom
fix/saml-xmlns-shard-bump

Conversation

@camreeves

@camreeves camreeves commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Steve's real-client test against UCLA Shibboleth failed with UCLA's "The login request you submitted is invalid" page on every attempt. Decoding the SAMLRequest from the redirect URL showed the AuthnRequest carried literal attributes named xmlns_samlp / xmlns_saml instead of real namespace declarations (Crystal named args cannot contain a colon), so the samlp:/saml: prefixes were unbound and OpenSAML refused the message before resolving the SP. That matches the blank "application name" on the error page. libxml2 parses the malformed XML leniently, which is why our own specs and round trips never caught it.

An audit for whatever else a real Shibboleth exchange would hit found that the response leg would have failed next, so this bump also picks up:

  • C14N kept dropping whitespace-only text nodes. Shibboleth (Apache Santuario) signs SignedInfo bytes that include newline text nodes, so its signatures could never verify.
  • Every serialization in the verify path used the default to_xml, which pretty-prints and injects whitespace the signer never saw. The two defects had been cancelling each other, which is why self-generated round trips always passed.
  • The reference transform's InclusiveNamespaces PrefixList (Shibboleth uses PrefixList="xsd") was removed along with the enveloped Signature before the digest was computed, guaranteeing a digest mismatch.
  • NotBefore / NotOnOrAfter validation ran with zero clock-skew tolerance; now 10s at the settings level, matching the legacy Ruby service's allowed_clock_drift.

All fixed on crystal-saml main (2644bd0 + 4b1bdc3 + d16bbb0) with regression specs whose expected canonical bytes are hand-derived rather than computed through the code under test. crystal-saml suite: 215 examples, 0 failures. Wire format verified end to end locally: generated a redirect URL with UCLA-shaped settings and decoded it exactly as the IdP does.

This PR is the shard.lock bump to d16bbb0. The lock entry is hand-written because shards update cannot complete on a host Crystal that fails ameba's postinstall; the entry matches what shards would write.

Before retesting on UCLA nonprod, two things to confirm on their side:

  1. The adfs_strat row must have idp_cert or idp_cert_fingerprint set. auth.cr fails closed without them, and the Ruby service working proves nothing here since its fallback validator accepted whatever certificate the response embedded.
  2. The SP registration at UCLA should not have an encryption certificate, since EncryptedAssertion is not yet supported. The Ruby-era registration had no SP cert so this should already be the case.

🤖 Generated with Claude Code

@camreeves
camreeves force-pushed the fix/saml-xmlns-shard-bump branch 2 times, most recently from 9e09b7b to ab22630 Compare August 17, 2026 00:10
@camreeves camreeves changed the title fix(saml): bump crystal-saml so generated messages declare their namespaces fix(saml): bump crystal-saml — namespaces + Shibboleth signature verification Aug 17, 2026
@camreeves
camreeves force-pushed the fix/saml-xmlns-shard-bump branch from ab22630 to 1cd39ed Compare August 17, 2026 00:34
…gnature verification

Two rounds of crystal-saml fixes found while chasing the failed UCLA
Shibboleth test, now pinned at spider-gazelle/crystal-saml@d16bbb0:

1. Generated messages carried literal xmlns_samlp/xmlns_saml attributes
   (Crystal named args cannot contain a colon), leaving the samlp:/saml:
   prefixes unbound. OpenSAML rejects the AuthnRequest before resolving
   the SP, which is exactly the UCLA error page observed.

2. Signature verification could not verify Apache Santuario (Shibboleth)
   shapes: C14N stripped whitespace-only text nodes that are part of the
   signed bytes, every to_xml in the verify path pretty-printed (injecting
   whitespace the signer never saw), and the reference transform's
   InclusiveNamespaces PrefixList was discarded with the enveloped
   Signature before digest computation.

3. NotBefore/NotOnOrAfter validation ran with zero clock-skew tolerance;
   the settings-level default is now 10s, matching the legacy Ruby
   service's allowed_clock_drift.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@camreeves
camreeves merged commit 70a286e into master Aug 17, 2026
7 checks passed
@camreeves
camreeves deleted the fix/saml-xmlns-shard-bump branch August 17, 2026 00:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant