Describe the bug
With spring-security 5.5.0+ authenticating with SAML fails with Saml2AuthenticationException{error=[malformed_response_data] No assertions found in response.} when EncryptedAssertion is signed but response is not signed.
Similar response authenticates properly with spring-security 5.4.6.
To Reproduce
Configure SAML response to Encrypt & Sign Assertion but keep the message/response unsigned.
Try to authenticate with OpenSamlAuthenticationProvider or OpenSaml4AuthenticationProvider.
Alternatively the following test method can be added to OpenSamlAuthenticationProviderTests to reproduce.
@Test
public void authenticateWhenEncryptedAssertionWithSignatureAndNoResponseSignatureThenItSucceeds() {
Response response = response();
Assertion assertion = TestOpenSamlObjects.signed(assertion(),
TestSaml2X509Credentials.assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID);
EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion,
TestSaml2X509Credentials.assertingPartyEncryptingCredential());
response.getEncryptedAssertions().add(encryptedAssertion);
Saml2AuthenticationToken token = token(response, decrypting(verifying(registration())));
this.provider.authenticate(token);
}
NOTE: with current main branch it seems that gradle tasks opensaml3Test and opensaml4Test don't seem to run any tests. This may be user error because I'm not a gradle expert.
In any case I add to move all the code & tests back to the main and test sourcesets to be able to run the tests.
Expected behavior
Authentication should proceed without errors.
Analysis
In 5.4.6
The method OpenSamlAuthenticationProvider.decryptAssertions is called even if Response is not signed.
This mutates the Response.getAssertions() list by adding the decrypted assertions.
Then OpenSamlAuthenticationProvider#validateAssertions checks that some assertion exist with
List<Assertion> assertions = response.getAssertions();
if (assertions.isEmpty()) {
throw createAuthenticationException(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA,
"No assertions found in response.", null);
}
In 5.5
In 5.5 the decryption logic has been moved to responseElementsDecrypter but the decryption is only called if response is signed.
boolean responseSigned = response.isSigned();
if (responseSigned) {
this.responseElementsDecrypter.accept(responseToken);
}
So at this stage the Response.getAssertions() contains the decrypted assertions only if the response is signed.
Then the responseSignatureValidator is called which does
if (response.getAssertions().isEmpty()) {
throw createAuthenticationException(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA,
"No assertions found in response.", null);
}
And this throws the exception.
Modifying code to always decrypting seems to fix the issue.
Describe the bug
With spring-security 5.5.0+ authenticating with SAML fails with
Saml2AuthenticationException{error=[malformed_response_data] No assertions found in response.}when EncryptedAssertion is signed but response is not signed.Similar response authenticates properly with spring-security 5.4.6.
To Reproduce
Configure SAML response to Encrypt & Sign Assertion but keep the message/response unsigned.
Try to authenticate with OpenSamlAuthenticationProvider or OpenSaml4AuthenticationProvider.
Alternatively the following test method can be added to OpenSamlAuthenticationProviderTests to reproduce.
NOTE: with current main branch it seems that gradle tasks opensaml3Test and opensaml4Test don't seem to run any tests. This may be user error because I'm not a gradle expert.
In any case I add to move all the code & tests back to the main and test sourcesets to be able to run the tests.
Expected behavior
Authentication should proceed without errors.
Analysis
In 5.4.6
The method OpenSamlAuthenticationProvider.decryptAssertions is called even if Response is not signed.
This mutates the Response.getAssertions() list by adding the decrypted assertions.
Then OpenSamlAuthenticationProvider#validateAssertions checks that some assertion exist with
In 5.5
In 5.5 the decryption logic has been moved to responseElementsDecrypter but the decryption is only called if response is signed.
So at this stage the Response.getAssertions() contains the decrypted assertions only if the response is signed.
Then the responseSignatureValidator is called which does
And this throws the exception.
Modifying code to always decrypting seems to fix the issue.