When IDP uses other URL-encoding than Spring, signature verification for a LogoutRequest coming from the IDP fails, because the content used for the signature computation on the RP-side is different than on the IDP-side.

We experience the following error scenario when trying to integrate with MS Azure: 1. Azure calls our logout endpoint with the query params SAMLRequest={samlRequest}&Signature={signature}&SigAlg=http%3a%2f%2fwww.w3.org%2f2001%2f04%2fxmldsig-more%23rsa-sha256. Note that slashes are escaped with the sequence %2f and this fact is incorporated into the signature. 2. Saml2LogoutRequestFilter intercepts the request and creates an instance of Saml2LogoutRequest. At this point all information about URL-encoding is lost, because Saml2LogoutRequest is filled with decoded URL params. 3. The Saml2LogoutRequest is later validated in the OpenSamlLogoutRequestValidator which under the hood reconstructs the content to be signed by concatenating the necessary query parameters and applying URL-encoding from Spring's UriUtils (see OpenSamlVerificationUtils.RedirectSignature). However, this results in a different content than the original one as Spring uses upper-case escape characters: SAMLRequest={samlRequest}&SigAlg=http%3A%2F%2Fwww.w3.org%2F2001%2F04%2Fxmldsig-more%23rsa-sha256 4. As a result the signature computed from the reconstructed query string doesn't match the signature computed by the IDP and world starts burning.

The SAML spec (page 17) states pretty clearly, that the unmodified values received in the query string should be used for the signature verification:

Further, note that URL-encoding is not canonical; that is, there are multiple legal encodings for a given value. The relying party MUST therefore perform the verification step using the original URL-encoded values it received on the query string. It is not sufficient to re-encode the parameters after they have been processed by software because the resulting encoding may not match the signer's encoding.

This behavior was observed in Spring Security 5.6.3.

Comment From: jzheaux

Thanks for the report, @markiewiczart.

I believe the unencoded query string can be obtained from the request, and so it may be appropriate for Saml2LogoutRequestFilter to provide this string in its construction of Saml2LogoutRequest in such a way that it can be used by OpenSamlVerificationUtils. I will take a look at addressing this for the upcoming point release.

In the meantime, you should be able to create a custom Saml2LogoutRequestValidator that validates the signature itself. If you like, it can delegate to the default validator for other checks.

Then, you can wire that validator in the DSL like so:

http
    .saml2Logout((saml2) -> saml2
        .logoutRequest((request) -> request.logoutRequestValidator(myValidator))
    );

Comment From: jzheaux

@markiewiczart, I've published a fix to the 5.8.x line. Are you able to download the snapshot and see if it addresses your issue? If you like, the snapshot should be available in about 20 minutes at: https://repo.spring.io/snapshot/org/springframework/security/spring-security-saml2-service-provider/5.8.0-SNAPSHOT

Comment From: markiewiczart

Hi @jzheaux, thanks for the lightning fast fix! This would be sufficient for my case. It seems, that the solution is also pretty spec-conform - the spec forbids any extra query parameters & specifies the order of the required params, so using the original query string with the Signature param removed should do the job 👍 The only breaking scenario would be an IDP explicitly specifying the encoding by including the param SAMLEncoding=urn:oasis:names:tc:SAML:2.0:bindings:URL-Encoding:DEFLATE, but it probably never happens in practice.