Skip to main content

Upgrading to 3.0 from 2.x

3.0.0 fixes an XML Signature Wrapping (XSW) vulnerability in signature validation and removes the API that made it possible to get it wrong. If your code validates signatures at all, you need to make the change below before upgrading.

Why

validateSignature() on ServiceProviderWrapper / IdentityProviderWrapper validated a flattened copy of the signature: it read the message's signature into a value/algorithm/data DTO, then re-verified that DTO as a reconstructed detached signature.

That worked fine for the HTTP-Redirect binding, where the signature really is detached (over the query string). But for HTTP-POST, the signature is an enveloped <ds:Signature> inside the XML document, and flattening it this way dropped LightSAML's XML Signature Wrapping defense — the check that the signed content is actually the element the application goes on to read, not a decoy.

Concretely: an attacker holding one genuinely IdP-signed Response could wrap it so the valid signature still covered a hidden copy of the real content, while a forged Response (different attributes, different NameID) sat in the position the application actually read. validateSignature() reported this as valid.

3.0.0 validates the live message instead, delegating to the signature reader's own validate() (SignatureXmlReader for POST, SignatureStringReader for Redirect), which runs the wrapping check. Because that check needs the live document, there's no way to preserve a "validate later, on the parsed DTO" API — validation now has to happen inline, during handle*().

1. Validate inline, not deferred

Before

$authnResponse = $spWrapper->handleAuthnResponse($request); // parsed, not validated

// ...later, once you know which IdP sent it
if (!$spWrapper->validateSignature($authnResponse, $idp)) {
throw new SamlException('Invalid signature');
}

After

use Litesaml\Exceptions\SamlException;

try {
$authnResponse = $spWrapper->handleAuthnResponse($request, validate: true, issuer: $idp);
} catch (SamlException $e) {
// Signature is missing or invalid
}

This applies to every handle*() method on both wrappers — handleAuthnRequest(), handleAuthnResponse(), handleLogoutRequest(), handleLogoutResponse(). See Verify signature for the full reference.

If your code was calling validateSignature() at all, you must know the expected issuer before parsing, not after — the issuer descriptor is now a required argument to handle*() rather than something you look up from the returned message first.

2. Drop signature reads

Message, AuthnRequest, AuthnResponse, LogoutRequest, and LogoutResponse no longer expose a signature property.

Before

if ($authnResponse->signature !== null) {
// ...
}

After

There's no replacement — message DTOs are now pure parsed data. Whether the signature was present and valid is answered entirely by whether handle*(validate: true, issuer: ...) threw.

3. Signature class removed

Litesaml\Models\Messages\Signature no longer exists. If you constructed one directly (for example in tests that built DTOs by hand), remove it — there's no replacement, since signatures are no longer represented as a flattened value/algorithm/data DTO.