Skip to content

0.5 to 0.6

Upgrade saml-rs, opensaml, samlify, or samlet from the latest 0.5.x release to 0.6.x.

The CBC response-signature relaxation changed names. What it does is unchanged. A new validation context allows five minutes of clock skew, and a required replay cache keeps a bearer assertion identifier until SubjectConfirmationData@NotOnOrAfter. RequireSignature now requires an enveloped #id metadata signature. A login response with a <Conditions> child other than <AudienceRestriction>, <OneTimeUse>, or <ProxyRestriction> is rejected. A response may contain several assertions when a signature covers them. An unsigned HTTP-Redirect that still contains a protocol ds:Signature returns SamlError::ProtocolProfile. A signed redirect removes that signature before DEFLATE. accept_unsolicited_sso rejects a RelayState longer than 80 bytes. A custom LogoutResponse template must carry the top-level status respond_slo writes. An exhaustive match on AudienceValidationPolicy or AuthnRequestValidationPolicy needs the new variant.

This breaks callers that name the old method or the old policy variant.

Who must change: code that calls RespondSso::allow_unsigned_encrypted_cbc_for_compatibility, and code that names ResponseSignaturePolicy::AllowUnsignedEncryptedCbcForCompatibility.

Call the method that allows an unsigned Response around a CBC-encrypted Assertion:

RespondSso::post().allow_unsigned_encrypted_cbc();

Select the same inbound relaxation with:

ResponseSignaturePolicy::AllowUnsignedEncryptedCbc

Both still relax SAML V2.0 Approved Errata 05 E93 only. They are not the Compatibility preset.

SpValidationPolicy::compatibility, IdpValidationPolicy::compatibility, and LogoutPolicy::compatibility keep their names. Compatibility is the legacy permissive preset: the samlify-port behaviour kept for a caller leaving the raw API. It does not claim standards conformance.

This breaks a service provider whose identity provider sends a <Conditions> child other than <AudienceRestriction>, <OneTimeUse>, or <ProxyRestriction>, including <Condition>.

Who must change: that integration. finish_sso and accept_unsolicited_sso reject the response. There is no switch that turns the check off.

<OneTimeUse> and <ProxyRestriction> stay valid. The classification is in SSO acceptance.

This breaks a service provider that treated a second <Assertion> as a rejected response.

Who must change: that integration. finish_sso and accept_unsolicited_sso accept the response when a signature covers every assertion and the assertions share one issuer and one principal. There is no switch that restores the rejection.

The principal comparison is NameID value, Format, NameQualifier, and SPNameQualifier. An omitted Format is urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified. The session comes from the first assertion with a bearer confirmation. Replay storage keeps every assertion identifier. The classification is in SSO acceptance.

A new SamlValidationContext allows five minutes of skew on NotBefore and NotOnOrAfter. That covers assertion conditions, bearer confirmation, AuthnStatement@SessionNotOnOrAfter, and LogoutRequest@NotOnOrAfter.

Who must change: code that calls SamlValidationContext::new without with_clock_skew and needs those instants enforced with no tolerance.

Keep zero skew:

let validation = SamlValidationContext::new(now, replay)
.with_clock_skew(ClockSkew::strict());

If you already set a skew, that value replaces the five minutes. ClockSkew::default stays zero. Raw clock_drifts are unchanged.

If receive_slo stores a LogoutRequest identifier, that deadline is now NotOnOrAfter plus the same skew. The allowance is classified in SSO acceptance and Logout.

Under ReplayPolicy::RequireCache, the bearer assertion identifier is stored until SubjectConfirmationData@NotOnOrAfter, plus the context NotOnOrAfter skew. An earlier Conditions or AuthnStatement instant does not shorten that identifier. The Response identifier is still stored and can still expire at the earlier instant.

Who must change: code whose cache drops an assertion identifier at the expires_at passed to ReplayCache::check_and_store, and that assumed that instant was the earliest of Conditions, bearer SubjectConfirmationData, and AuthnStatement.

ReplayPolicy::DisabledForCompatibility still stores nothing. Keep that hatch:

let validation = SamlValidationContext::new(
now,
ReplayPolicy::DisabledForCompatibility,
);

This breaks callers that import metadata with MetadataTrustPolicy::RequireSignature when the signature is not an enveloped signature with one #id reference to the root, when it contains a ds:Object, or when a reference transform is not enveloped-signature or exclusive canonicalization. A root EntitiesDescriptor is checked the same way.

Who must change: code that imports metadata with MetadataTrustPolicy::RequireSignature.

If the signature uses inclusive canonicalization, select it and leave the other checks on:

MetadataTrustPolicy::RequireSignatureAllowingOtherCanonicalization {
trusted_certificates: &certificates,
}

For any other rejected shape, sign the metadata root with an enveloped #id reference and exclusive canonicalization. The accepted shapes are recorded in Metadata and replay.

An exhaustive match on MetadataTrustPolicy needs an arm for RequireSignatureAllowingOtherCanonicalization. UnsignedForCompatibility is unchanged.

This breaks a typed HTTP-Redirect whose protocol element still has a direct child ds:Signature.

Who must change: start_sso, start_slo, and respond_slo on that binding, including a custom template that embeds the signature.

An unsigned redirect returns SamlError::ProtocolProfile. Remove the protocol signature and attach the detached query signature.

A signed redirect removes that signature before raw DEFLATE. The detached signature covers SAMLRequest or SAMLResponse, then RelayState when it is present, then SigAlg. A signature nested on an assertion stays in the XML. Verify the query signature. The classification is in SSO generation and Logout.

This breaks an unsolicited login response whose RelayState is longer than 80 bytes.

Who must change: the caller of accept_unsolicited_sso or accept_unsolicited_sso_with_outstanding_logout. The call returns SamlError::Invalid. Shorten RelayState to 80 bytes, or omit it.

finish_sso, receive_sso, and typed Single Logout already enforced this limit. The classification is in SSO acceptance.

This breaks a TemplatePolicy whose logout_response_template does not carry the top-level status respond_slo writes.

Who must change: a session participant or session authority that sets that field on SpConfig or IdpConfig. Omitting RespondSlo::status still writes urn:oasis:names:tc:SAML:2.0:status:Success.

Keep the placeholder on the top-level status code:

<samlp:Status>
<samlp:StatusCode Value="{StatusCode}"/>
</samlp:Status>

{StatusCode} becomes that URI. After substitution, the top-level StatusCode must equal the status supplied to respond_slo. A template that keeps a different value returns SamlError::Invalid.

{StatusCode} holds only the top-level code. To send a subordinate code, omit logout_response_template and pass RespondSlo::status. The default renderer writes the top-level code and one subordinate code.

This breaks an exhaustive match on AudienceValidationPolicy or AuthnRequestValidationPolicy.

Who must change: that match. Add AudienceValidationPolicy::EvaluatePresentRestrictions and AuthnRequestValidationPolicy::AllowUnsignedVerifyIfPresent.

Default, new, try_new, and compatibility() still return the 0.5 variants. Config builders still start from strict(). recommended() selects the new variants. Match SkipForCompatibility and AllowUnsignedForCompatibility by name: inserting the new variants changed their numeric discriminants.

The following release removes strict() and points Default, new, try_new, and the config builders at Recommended together.

Who must change: code that calls SpValidationPolicy::strict, IdpValidationPolicy::strict, or LogoutPolicy::strict. In that same release, code that depends on Default, new, or try_new staying on Compatibility must change, as must code that depends on a config builder staying on the strict() bundle.

Start from recommended() and add only the named hardenings you want. A direct Assertion signature and the RSA-SHA2 XML-DSig profile stay independent options. Neither is implied by recommended(), and strict() does not gain the RSA-SHA2 profile. Set one field, the other, or both:

let require_assertion_signature = SpValidationPolicy {
assertions: AssertionSignaturePolicy::RequireSigned,
..SpValidationPolicy::recommended()
};
let require_rsa_sha2 = SpValidationPolicy {
xml_signatures: XmlSignatureProfile::StrictRsaSha2,
..SpValidationPolicy::recommended()
};

LogoutPolicy::recommended() is the replacement for LogoutPolicy::strict(). Both require a signature on logout requests and responses.

SpValidationPolicy::compatibility, IdpValidationPolicy::compatibility, and LogoutPolicy::compatibility stay available by name. Raw settings defaults stay unchanged.