Skip to content

0.2 to 0.3

Upgrade from the latest 0.2 release to 0.3. This release does not change feature flags or the minimum supported Rust version. The changelog lists every other change.

Do these three updates wherever they appear in your crate:

  1. Rename the response-signature policy types.
  2. Replace public time crate types with std::time.
  3. Decide whether typed IdP responses must carry an embedded signature.

MessageSignaturePolicy and SpValidationPolicy::messages are removed. There is no deprecated alias.

Before:

use saml_rs::{MessageSignaturePolicy, SpValidationPolicy};
let mut validation = SpValidationPolicy::strict();
validation.messages = MessageSignaturePolicy::RequireSigned;

After:

use saml_rs::{ResponseSignaturePolicy, SpValidationPolicy};
let mut validation = SpValidationPolicy::strict();
validation.responses = ResponseSignaturePolicy::RequireSigned;

The replacement variants are:

  • RequireSigned, which authenticates every Response;
  • RequireForEncryptedCbc, which signs a Response that contains a CBC-encrypted Assertion;
  • AllowUnsignedEncryptedCbcForCompatibility, which relaxes that CBC case. This variant is the default of the standalone enum. From 0.6, use AllowUnsignedEncryptedCbc and RespondSso::allow_unsigned_encrypted_cbc (0.5 to 0.6).

SpValidationPolicy::strict() still requires a trusted signature directly over the consumed Assertion, and it uses ResponseSignaturePolicy::RequireForEncryptedCbc. Plaintext responses do not need Response-root coverage unless you opt in:

use saml_rs::{ResponseSignaturePolicy, SpValidationPolicy};
let validation = SpValidationPolicy {
responses: ResponseSignaturePolicy::RequireSigned,
..SpValidationPolicy::strict()
};

RequireSigned is satisfied only by trusted signature evidence over the Response root. An Assertion-only signature does not satisfy it. A verified HTTP-Redirect detached signature, or an HTTP-POST-SimpleSign CD04 signature, still satisfies the response-message requirement on those bindings.

When one of those binding-level messages is authenticated, the typed receiver requires Response@Destination and rejects both a missing value and a mismatch. An unsigned HTTP-POST Response whose Assertion is signed directly does not pick up that Destination requirement.

A signature directly over the Assertion is saml-rs policy. It matches WantAssertionsSigned in SAML metadata. Why a signed Response without a signed Assertion can still be valid is explained in standards conformance.

Public validation and replay APIs now take std::time::SystemTime and std::time::Duration. Parsing still uses the time crate internally. The affected APIs are:

  • SamlValidationContext::new and SamlValidationContext::now;
  • SamlValidationContext::with_replay_retention and SamlValidationContext::replay_retention;
  • ReplayCache::check_and_store;
  • FlowOptions::now on the raw compatibility API.

An unsupported validation instant now returns SamlError instead of panicking. An invalid or unrepresentable replay expiration, including zero retention and a retention that overflows SystemTime, returns SamlError::TimeWindowInvalid with TimeWindowField::ReplayExpiration.

Replace OffsetDateTime::now_utc() with SystemTime::now(), and replace time::Duration constructors with std::time::Duration.

Before:

use saml_rs::{ReplayPolicy, SamlValidationContext};
use time::{Duration, OffsetDateTime};
let validation = SamlValidationContext::new(
OffsetDateTime::now_utc(),
ReplayPolicy::DisabledForCompatibility,
)
.with_replay_retention(Duration::minutes(5));

After:

use std::time::{Duration, SystemTime};
use saml_rs::{ReplayPolicy, SamlValidationContext};
let validation = SamlValidationContext::new(
SystemTime::now(),
ReplayPolicy::DisabledForCompatibility,
)
.with_replay_retention(Duration::from_secs(5 * 60));

Build a fixed test clock from SystemTime::UNIX_EPOCH with checked_add or checked_sub.

check_and_store now takes a SystemTime expiration.

Before:

use saml_rs::{ReplayCache, ReplayKey, SamlError};
use time::OffsetDateTime;
struct Cache;
impl ReplayCache for Cache {
fn check_and_store(
&mut self,
key: ReplayKey,
expires_at: OffsetDateTime,
) -> Result<(), SamlError> {
let _ = (key, expires_at);
Ok(())
}
}

After:

use std::time::SystemTime;
use saml_rs::{ReplayCache, ReplayKey, SamlError};
struct Cache;
impl ReplayCache for Cache {
fn check_and_store(
&mut self,
key: ReplayKey,
expires_at: SystemTime,
) -> Result<(), SamlError> {
let _ = (key, expires_at);
Ok(())
}
}

Store the key and expiration atomically. If the cache uses another timestamp type, convert at the storage boundary. Keep subsecond precision, and keep instants before the Unix epoch when the backend can store them.

Assign a SystemTime when you pin the validation instant:

use std::time::SystemTime;
use saml_rs::raw::FlowOptions;
let mut options = FlowOptions::default();
options.now = Some(SystemTime::now());

Drop a direct time dependency after every call site has moved, if the application used time only for these APIs. Keep it when the rest of the application still needs it.

A typed IdP signs the top-level Response when it generates an EncryptedAssertion with a known CBC-mode algorithm. To force an embedded XML signature on any HTTP-POST response:

let options = RespondSso::post().sign_response();
let response = idp.respond_sso(sp, &request, subject, options)?;

sign_response() does not replace HTTP-POST-SimpleSign. That binding still uses its detached signature.

To keep an unsigned CBC-encrypted response for a legacy peer:

let options =
RespondSso::post().allow_unsigned_encrypted_cbc_for_compatibility();
let response = idp.respond_sso(sp, &request, subject, options)?;
  • Replace MessageSignaturePolicy and SpValidationPolicy::messages.
  • Replace public OffsetDateTime validation values with SystemTime.
  • Replace public time::Duration replay values with std::time::Duration.
  • Update every ReplayCache::check_and_store implementation.
  • Update raw FlowOptions::now assignments.
  • Remove a direct time dependency when no application code still uses it.
  • Choose embedded Response signing for typed IdP HTTP-POST output, or opt out for a legacy CBC peer.
  • Run validation and replay tests, including fixed-clock and expiration cases.