saml 0.0.1-alpha.1

Stateless, async-native SAML 2.0 toolkit with no libxml2/xmlsec C build chain
Documentation
# Pre-release ship list

Flat list of things that need to ship before `saml` is meaningfully
released. No tiers. Each item is either a real interop bug, a verification
gap that makes a current claim performative, or a process step that has
to happen to get bits onto crates.io.

> **Status (2026-05-29).** Done: 1 (already implemented), 2, 3, 4, 5, 6, 7, 8,
> 9, 10, 11. Repo CI is green. Toolchain pinned via `mise.toml` (rust 1.95.0 +
> cargo-fuzz). All items done; releases now run via docs/releasing.md flow.

## Code gaps

1. **Inclusive C14N.** ✅ DONE — already fully implemented (`src/dsig/c14n.rs`,
   `C14nAlgorithm::InclusiveCanonical{,WithComments}`) with W3C example +
   Keycloak regression tests. Plan item was stale.

2. **Encrypted NameID.** ✅ DONE — standalone `EncryptedID` now decrypted on
   `<LogoutRequest>` (SP + IdP `consume_logout_request`) and assertion
   `<Subject>` (response validation), after signature verification, gated by
   `PeerCryptoPolicy`. Reuses the EncryptedAssertion xmlenc plumbing.

3. **Encrypted assertions actually exercised in Rust IdP e2e.** ✅ DONE — IdP
   encrypts the assertion when the SP advertises an encryption cert (env-gated
   `SAML_IDP_FORCE_ENCRYPT`); `tests/encrypted_assertion_flow_test.rs` proves
   IdP-encrypt → SP-decrypt in-process (asserts the wire carries
   `<saml:EncryptedAssertion>` and no cleartext NameID).

4. **Artifact binding in Rust IdP.** ✅ DONE — `handle_artifact` now serves the
   SOAP ArtifactResolve → signed ArtifactResponse round trip with a bounded,
   one-time-consume `ArtifactStore` + cross-SP reuse defense;
   `examples/idp/tests/artifact_binding.rs` exercises it through the real axum
   handler (under `--features artifact-binding`).

5. **IdP-initiated SLO in Rust IdP.** ✅ DONE — `POST /logout-everywhere`
   builds a signed `<samlp:LogoutRequest>` to a participating SP and consumes
   the returning `<samlp:LogoutResponse>`, clearing the IdP session;
   `examples/idp/tests/idp_initiated_slo.rs` proves the loop (single-SP demo
   topology; multi-SP fan-out documented as out of scope).

## Verification

6. **`cargo-fuzz` real runtime.** ✅ DONE — 30 min/target on Apple silicon,
   zero crashes: `fuzz_xml_parse` 18.0M runs, `fuzz_c14n` 19.4M runs,
   `fuzz_base64_response` 28.0M runs. **Finding:** `fuzz_base64_response` was
   silently broken — its `#[cfg(fuzzing)]` harness didn't compile after
   `ReplayMode` added a `replay_mode` field to `ConsumeResponse`, so it had
   spent zero CPU. Fixed, re-ran the full 30 min, and added a CI `fuzz-build`
   job so a stale target fails CI instead of going unnoticed.

7. **`xmlsec1` cross-check on emitted metadata signature.** ✅ DONE —
   `tests/xmlsec_interop.rs`: `xmlsec1 --verify` accepts our IdP + SP metadata
   signatures, with a tamper negative-control so it can't pass vacuously. CI
   installs `xmlsec1` and runs it.

8. **Live import of our metadata into Keycloak or Shibboleth as IdP /
   SP descriptor.** ✅ DONE — Keycloak 26 (docker) accepts our emitted SP
   metadata as a SAML client and our IdP metadata as an identity provider; all
   four admin-API calls returned 2xx with no rejected/warned fields. Procedure
   + gated driver in `examples/idps/KEYCLOAK_INTEROP.md` / `keycloak_interop.sh`.

9. **Auth0 / Asgardeo / Descope end-to-end re-run.** ✅ DONE — re-ran the live
   demo SSO via the browser: **Auth0 ✅** and **Asgardeo ✅** complete the full
   Web-Browser-SSO round trip post-Tier-2 (assertion verified, identity +
   attributes extracted). **Descope ✅** — full round trip completed via
   Descope's Google login: SP dashboard reached, assertion verified, NameID +
   4 attributes extracted. The `E068003` seen at first was a
   test-environment artifact: the same browser was logged into the **Descope
   admin console**, and those session cookies, carried into the SSO endpoint,
   triggered the failure. Proven by isolating the variables: signed-no-session
   AND unsigned-no-session both reach the login page; signed AND unsigned both
   fail `E068003` when the console session is present. No code change needed —
   request signing was never the cause. Zitadel not re-tested.

## Release ceremony

10. **Git remote.** ✅ DONE — public repo `danielkov/saml` created, `origin`
    added, `main` pushed. Cargo.toml `repository` / `homepage` URLs resolve.

11. **`cargo publish --dry-run -p saml`** ✅ DONE — passes on current `HEAD`
    (packages + verifies the tarball). Also trimmed the package by excluding
    third-party `tests/corpus/` fixtures: 324 → 87 files, 714 → 356 KiB
    compressed.

12. **`cargo publish -p saml`** ✅ DONE — `v0.0.1-alpha.0` published
    2026-05-28. Subsequent releases are automated: tag push → verify →
    `crates-io` environment approval → Trusted Publishing (OIDC). See
    `docs/releasing.md`.

13. **Push version tag** ✅ SUPERSEDED — `v0.0.1-alpha.0` shipped untagged;
    from `v0.0.1-alpha.1` on, the tag push *is* the release trigger
    (`docs/releasing.md`).