This shard is a Crystal port of ruby-saml. Versions before 0.2.0 carried ruby-saml's sign-in bypass (CVE-2024-45409) and must not be used.
-
Strict parsing. XML is parsed by libxml2 with network access off and without RECOVER, DTD loading or entity substitution. A DOCTYPE, an entity, a namespace error or any parser warning rejects the message. The same parser reads messages, decrypted elements and metadata.
-
One signature at a time. Every lookup (SignedInfo, Reference, DigestValue, SignatureValue, KeyInfo) is relative to the signature being checked. A signature must be a direct child of the element it signs, and must have:
- one Reference to that element's ID, or
""for the root; - at most an enveloped transform plus one canonicalization transform;
- supported algorithms only.
The referenced ID must appear exactly once in the document.
- one Reference to that element's ID, or
-
Configured certificates only. Signatures are checked against
idp_cert, or theidp_cert_multisigning certificates (two are allowed during a rollover). A KeyInfo certificate that differs from them rejects the message. The document's own certificate is used only in legacy fingerprint mode (idp_cert_fingerprint), and only once its fingerprint matches, compared in constant time. -
Data from signed bytes. NameID, attributes, conditions and SessionIndex are read from a fresh parse of the canonical bytes the digest covers, never from the received tree. When a certificate or fingerprint is configured, the getters return nothing from a signature that doesn't verify.
-
Every signature checked. The Response-level signature and the Assertion signature (after decryption) are both checked when present. One of them is required, and
want_assertions_signedrequires the Assertion's. -
Digests compared in constant time. An empty canonical form is an error.
-
Size limits. Messages over
message_max_bytesize(250,000 bytes by default) are refused before any regexp or base64 work, and inflation stops at the same limit. -
Schema validation. Responses and logout messages are validated against the SAML 2.0 XSDs shipped in
src/schemas, with libxml2's schema validator. -
Decryption. AES-GCM tags are always checked. Decryption errors are reported with one generic message.
| Advisory | Status |
|---|---|
| CVE-2024-45409: XPath selected the wrong DigestValue/signature | Fixed: all lookups are relative to their signature (tests in spec/security_test.cr) |
| CVE-2025-25291, CVE-2025-25292: parser differential (REXML vs Nokogiri) | Not applicable, since only libxml2 parses. DOCTYPE/ATTLIST tricks are refused anyway |
| CVE-2025-25293: compressed message DoS | Fixed: inflation is capped at message_max_bytesize |
| CVE-2025-54572: large message DoS before the size check | Fixed: the size check runs before any regexp or decoding |
| CVE-2025-66567: incomplete fix for CVE-2025-25292 | Not applicable (single parser); DOCTYPEs are refused |
| CVE-2025-66568: libxml2 canonicalization returning an empty string | Fixed: empty canonical output is an error |
| CVE-2017-11428: comments splitting node text | Fixed: text joins every text and CDATA child |
- Fetching remote metadata.
IdpMetadataParser#parse_remotefetches any http(s) URL it is given. When the URL comes from a user, check it first (scheme, host, no internal addresses). Redirects are not followed, there is a 10-second timeout and a 10 MB cap (:max_bytesize), and TLS is verified unlessvalidate_certis false. - Older encryption algorithms. AES-CBC and RSA-1.5 key transport remain supported for compatibility and are open to padding-oracle attacks in principle. OpenSSL 3.2 and later use implicit rejection for RSA-1.5. Prefer IdPs that use AES-GCM with RSA-OAEP.
- Logout messages. As in ruby-saml, a LogoutRequest or LogoutResponse signature is
only checked when the HTTP-Redirect
Signatureparameter is present (:get_params). - Crystal version. Only Crystal 1.21 has been tested.
Please report vulnerabilities privately to the maintainer rather than in a public issue.