-
Notifications
You must be signed in to change notification settings - Fork 6.1k
Add HSM Support for Decrypting Assertions #9044
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Currently, So, instead of: Decrypter decrypter = // ... your custom decrypter
provider.setDecrypter((token) -> decrypter); you'd do: YourDecrypter decrypter = // ... your custom decrypter
provider.setResponseDecrypter((tuple) -> {
EncryptedAssertion encrypted = tuple.getResponse().getEncryptedAssertions().get(0);
Assertion assertion = decrypter.decrypt(encrypted);
tuple.getResponse().getAssertions().add(assertion);
}); Or, for decrypting an assertion's subject, you'd do: YourDecrypter decrypter = // ... your custom decrypter
provider.setAssertionDecrypter((tuple) -> {
EncryptedID encrypted = tuple.getAssertion().getSubject().getEncryptedID();
NameID name = decrypter.decrypt(encrypted);
tuple.getAssertion().getSubject().setNameID(name);
}); While a touch more verbose, it provides a great deal more flexibility and encourages composition over inheritance. |
Thanks for the PR, @ryan13mt, I've left my feedback there. |
- Renamed to setResponseDecrypter and setAssertionDecrypter to align with ResponseToken and AssertionToken - Changed contract of setAssertionDecrypter to use AssertionToken - Changed assertions in unit test to use isEqualTo Issue spring-projectsgh-9044
- Renamed to setResponseElementsDecrypter and setAssertionElementsDecrypter to align with ResponseToken and AssertionToken - Changed contract of setAssertionElementsDecrypter to use AssertionToken - Changed assertions in unit test to use isEqualTo Issue gh-9044
Hello @jzheaux thanks for closing this issue and merging to master. I am finding a problem with the polishing commit. You added a check in d0581c9#diff-e1434c9a229d033aa3fe922ab7b9637a54ba5a2521711e3b362e70bc7f787833R511 where you are checking if a response is signed and if true, doing the elements decryption. Is there a reason this check was added? I believe this is not correct since there are use cases where the idp will sign and encrypt the assertion but will leave the response unsigned as is in our case. Thus having an unsigned SAML Response with an encrypted signed Assertion should still decrypt the assertion and then checking that it has been signed before throwing the error "Either the response or one of the assertions is unsigned. Please either sign the response or all of the assertions.". or in my case it's throwing "No assertions found in response." since the encrypted assertions have not being decrypted yet. With this new check, the library is now requiring that a response must be signed if it contains an encrypted assertion. Thanks |
Thanks, @ryan13mt, for the feedback. If the response is unsigned, there is no guarantee that the encryption was not altered in transit (see section 6.2), which appears unsafe to me. Can you explain why you need the response to remain unsigned? Or, that is, what's the rationale for signing and then encrypting the assertions instead of encrypting the assertions and then signing the response? |
@jzheaux i agree completely. Saying that, i dont feel the Spring library should block something that is still technically allowed by the SAML protocol. I found that these scenarios are all allowed by the protocol although some should never be used due to the obvious security concerns:
My main concern about this check is that it will technically be a breaking change for users(like me) that have already onboarded clients and will then force us to go to each individual Asserting Party and tell them to change configuration for SSO to work. In the Spring-Security Documentation 13.1.3 point 6 of the diagram states: "After that, the provider verifies the signature of each Assertion. If any signature is invalid, authentication fails. Also, if neither the response nor the assertions have signatures, authentication fails. It is required that either the response or the assertions have signatures." Not sure on the way forward for this. Maybe we can set an explicit configuration on the OpenSamlAuthenticationProvider class to allow this scenario? That way the users will have to configure it manually and thus knowing the risks involved in doing so. It will of course not allow it by default but at least provide a way for users to still be able to do so if they choose. Would like to thank you again Josh for all your time and knowledge on this subject. I am fairly new to this and have learned a lot from these discussions with you. |
Good comments, @ryan13mt
It's important to remember that some of those configurations are for other exchange mechanisms, like over TLS on the backchannel. It's not meant that they be allowed universally. For example, Spring Security supports the WebSSO profile POST binding and probably won't support others for a while yet, so having everything unsigned is not a scenario we want to try and simplify.
How common of a setup (sign-then-encrypt) is this for you and what was the motivation for choosing that order? The reason I ask is two-fold:
|
Thanks for the reply @jzheaux. Most of our clients use RSA-OAEP or RSA1_5 |
Sorry, I wasn't clear with my question, @ryan13mt. I think asymmetric algorithms are intended for encrypting the session key (see the What is the symmetric algorithm used to encrypt the assertion itself? (see the |
Hi @jzheaux In our app we expose a configuration UI that allows our customers to configure their SSO (SAML, OpenID) and we internally rely on spring-security to do the authentication. Between two versions of our app we upgraded spring-security from 5.4 to 5.5 to benefit from some of the new features but it broke the authentication for some of our clients that did configure their SAML server to sign the Encrypted Assertion but did not sign the SAML response. I understand that spring-security wants to prevent unsafe configuration and that's probably a good thing. However in this case this is a breaking change so
Also if this decision is made I think that when response is not signed but contains EncryptedAssertion authentication should fail with a clear message instead of "No assertions found in response." because this message is very misleading. |
@sarod, thanks for the extra explanation. Agreed that additional documentation about it being a breaking change would help, and I've added #10219 to address this. Since decrypting an unsigned response is demonstrably insecure in common cases, I'm hesitant to further simplify doing this. But there are some things I think we can do: First, if AES-GCM encryption was used, I believe that Spring Security code could be modified to allow decryption without a response signature. Is that your case? Second, if not, you can make the allowance by decrypting the response yourself like so: public class MyAuthenticationProvider implements AuthenticationProvider {
OpenSaml4AuthenticationProvider delegate = new OpenSaml4AuthenticationProvider();
@Override
public Authentication authenticate(Authentication authentication) {
Saml2AuthenticationToken token = (Saml2AuthenticationToken) authentication;
Response response = parse(token.getSaml2Response());
decrypt(response);
Saml2AuthenticationToken decrypted = new Saml2AuthenticationToken(
token.getRelyingPartyRegistration(), serialize(response));
return this.delegate.authenticate(decrypted);
}
public boolean supports(Authentication authentication) {
return this.delegate.supports(authentication);
}
private Response parse(String response) {
try {
ParserPool parserPool = XMLObjectProviderRegistrySupport.getParserPool();
InputStream responseStream = new ByteArrayInputStream(response.getBytes(StandardCharsets.UTF_8));
return (Response) XMLObjectSupport.unmarshallFromInputStream(parserPool, responseStream);
}
catch (Exception ex) {
throw new Saml2AuthenticationException(...);
}
}
private void decrypt(Response response) {
DecryptionParameters parameters = new DecryptionParameters();
// ... set parameters as needed
Decrypter decrypter = new Decrypter(parameters);
EncryptedAssertion encrypted = response.getEncryptedAssertions().get(0);
try {
Assertion assertion = decrypter.decrypt(encrypted);
response.getAssertions().add(assertion);
response.getEncryptedAssertions().remove(encrypted);
} catch (Exception e) {
throw new Saml2AuthenticationException(...);
}
}
private String serialize(Response response) {
try {
Element element = XMLObjectSupport.marshall(response);
return SerializeSupport.nodeToString(element);
}
catch (MarshallingException ex) {
throw new Saml2AuthenticationException(...);
}
}
} Finally, yes I agree that the error message should improve, and I've opened #10220 to address that. |
Hi @jzheaux, To answer your question it seems that our client is using AES-CBC and so they are indeed using an insecure setup. The provided workaround using a wrapping AuthenticationProvider should allow us to re-enable this insecure setup to give time for our customers to migrate to a more secure configuration so that's great. Thanks for the detailed explanation and workaround. |
@jzheaux Hi. |
Hi @jzheaux Unfortunately the proposed workaround fails. It results in an Saml2AuthenticationException with message:
|
I fixed it by changing the configuration of the decrypt
|
@fink-artem I tried the updated workaround but I still have problems with Azure AD: |
@fink-artem Sorry, nevermind! I had wrongly configurated decryption credentials in relying party registration. After fixing it the workaround works fine!. |
It would be nice to allow for a custom decryption strategy in
OpenSamlAuthenticationProvider
. This would simplify delegating the assertion and principal decryption to a separate service.The text was updated successfully, but these errors were encountered: