Wednesday, February 2, 2011

Support for SAML2 assertions in WSS4J 1.6

Support for SAML2 assertions has finally arrived in WSS4J, via the forthcoming 1.6 release. This has been a long-standing feature request (see here). WSS4J 1.5.x only supports SAML 1.1 assertions via the deprecated Opensaml1, and it supports them in a very limited manner, namely:
  • It only supports the creation of Authentication statements.
  • Processing essentially involves saving the assertions, it did not support validating enveloped signatures, or trust on the signatures, etc.
Several patches were submitted to WSS-146 to upgrade WSS4J to use Opensaml2. I took a patch from Todd Michael Dunst as a starting point for the port, as his patch had the nice idea of using a CallbackHandler implementation to construct outbound SAML assertions. About a months work later (mainly in testing, refactoring, and porting the patch to trunk) the SAML2 port is more or less ready on trunk. SAML2 support in WSS4J 1.6 consists of:
  • Support for creating signed/unsigned SAML 1.1/2.0 assertions, containing authentication, authorization, attribute statements etc.
  • This extensibility is achieved by letting the user implement a CallbackHandler instance.
  • The SAMLTokenProcessor can now process any type of assertion, verify an enveloped signature on it, and verify trust on the signature. It also verifies some holder-of-key requirements, e.g. that the Subject contains a KeyInfo element, and that the assertion is signed and trusted etc.
WSS4J 1.6 contains an extensive set of tests for both creating and processing different type of assertions, you can browse them here. To illustrate the flexibility and simplicity of the CallbackHandler approach for constructing assertions, take a look at an abstract CallbackHandler here, as well as the concrete implementations (SAML 1.1 and SAML 2). As you can see, a fairly small amount of code can create a large variety of assertions.

Opensaml2 has a very large set of dependencies, but through some judicious pom exclusions, as well replacing the Opensaml DefaultBootstrap code to avoid loading velocity, the following dependencies are introduced in WSS4J via Opensaml (snippet from mvn dependency):
+- org.opensaml:opensaml:jar:2.4.1:compile
 |  \- org.opensaml:openws:jar:1.4.1:compile
 |     \- org.opensaml:xmltooling:jar:1.3.1:compile
 |        +- org.slf4j:slf4j-api:jar:1.6.1:compile
 |        \- joda-time:joda-time:jar:1.6.2:compile
The WSS4J 1.6 pom currently has a dependency on the Shibboleth repo, where the Opensaml2 artifacts live. I plan on getting the Opensaml2 artifacts into Maven central in time for the 1.6 release - this is slightly complicated by the fact that some of the Opensaml2 dependencies are themselves not in Maven Central.

One known issue is that WSS4J cannot create an Assertion which has an EncryptedKey element in the Subject. This is due to a bug in Opensaml2 which has been fixed, but not released yet.

The Opensaml2 port has a large impact on existing code for *creating* assertions, however I suspect that very few people used that code. It has a minimal impact on existing code for processing assertions, with several caveats:
  • WSS4J 1.5.x ignored (enveloped) signatures on SAML (1.1) assertions - this is no longer the case, so deployments which do not set the correct keystore/truststore config for dealing with signature verification will fail
  • The SAMLTokenProcessor no longer saves all tokens as an "WSConstants.ST_UNSIGNED" action. It saves tokens that do not have an enveloped signature as this action, and token which *do* have an enveloped signature are saved as a "WSConstants.ST_SIGNED" action.
  • The object that is saved as part of the action above has changed, from an Opensaml1 specific Assertion object, to an AssertionWrapper instance, which is a WSS4J specific object which encapsulates an Assertion, as well as some information corresponding to signature verification, etc.

20 comments:

  1. Hi Colm,

    I created an STS using the CXF framework that accepts a SAML token from a trusted third party and returns a signed SAML bearer token. The STS will reject a tampered SAML tokens or a SAML tokens signed by an untrusted party. Unfortunately, and quite surprisingly, the STS will ALSO happily accept an UNSIGNED SAML token.

    Can this be right? Here is the code in WSS4J SamlAssertionValidator class where the validation occurs:

    // Verify trust on the signature
    if (assertion.isSigned()) {
    verifySignedAssertion(assertion, data);
    }
    return credential;

    How do I change this behavior so that only signed SAML tokens are accepted? Do I have to create my own SamlAssertionValidator?

    ReplyDelete
  2. What version of CXF are you using? The latest versions of CXF have (in the SAMLTokenValidator in the STS):

    if (!assertion.isSigned()) {
    LOG.log(Level.WARNING, "The received assertion is not signed, and therefore not trusted");
    return response;
    }

    Colm.

    ReplyDelete
  3. We're using CXF 2.7.3.

    Based on what I know from stepping through the code in the debugger, when the RST arrives at org.apache.cxf.ws.security.sts.provider.SecurityTokenServiceProvider, only the Issue operation is called not the Validate operation. Therefore, there is no way that SAMLTokenValidator gets called on the RST issue operation.

    In fact, the only code executed is the WSS4J SamlAssertionValidator and the CXF SAMLTokenProvider. Neither of which complains if the token is not signed.

    I think this is a serious problem.

    ReplyDelete
  4. Could you please try with a more up to date version of CXF + let me know what happens? CXF 2.7.3 is quite old now + contains known security issues...

    Colm.

    ReplyDelete
  5. 1. Trying this with an up to date version f CXF would require a monumental amount of effort to change software that is only partially in my control. Imagine a scenario where you have to update a critical component for an existing product with an incredibly dense dependency tree. Upgrading a library like CXF might take a day but it could also take a week or more.

    2. I gave you the two classes where the problem exits. SamlAssertionValidator.java in WSS4J and SecurityTokenServiceProvider in CXF. It only takes few minutes to look up more recent versions of code in WSS4J (2.1.0) and CXF (2.7.12) and determine that they have not changed in ways that would mitigate this issue.

    3. This issue is easy enough to mitigate by writing a few lines of custom code. However, there are two reasons why I am bringing this to your attention:

    A. There may be an easier way to mitigate this that I do not know of. The inner security mechanisms of CXF and WSS4J are not exactly thoroughly documented. Your blogs certainly help, but in my experience, significant time spent looking at code is required to delve into things and the code is so dense it could take weeks to fully understand.

    B. This is potentially a pretty serious security issue. If you could please spend some time investigating this I think it would benefit the whole community. I also think it would be more productive than the typical process for dealing with potential vulnerabilities.

    ReplyDelete
  6. What is your use-case exactly? Are you calling the issue binding of the STS and passing the other SAML Token as OnBehalfOf/ActAs? Or are you calling the validate binding + including the SAML Token in the security header as per a security policy requirement?

    Colm.

    ReplyDelete
  7. 1. Yes I am calling the issue binding.

    2. I am using the CXF STSClient.

    3. I am setting the SecurityConstants.SAML_CALLBACK_HANDLER property to a CallbackHandler implementation that supplies the SAML token used to authenticate with the STS.

    ReplyDelete
  8. Which of the following cases applies?

    a) The WS-Trust request has an "ActAs" or "OnBehalfOf" Element which includes (or references the SAML Assertion in the security header)?

    b) There is no reference to a SAML Assertion in the WS-Trust request. Instead you are including a SAML Assertion in the security header of the request? In this case, what does the security policy of the STS endpoint require?

    Colm.

    ReplyDelete
  9. This comment has been removed by the author.

    ReplyDelete
  10. First off all, thank you very much for helping out.

    a) There is no ActAs or OnBehalfOf Element, so this does not apply.

    b) I was initially suspicious that the problem might be a WS-Policy issue but everything I tried seem not to have an effect. Please see the WSDL snippet below.

    <wsp:Policy wsu:Id="Transport_policy_saml">
    <wsp:ExactlyOne>
    <wsp:All>
    <wsap10:UsingAddressing />
    <sp:TransportBinding
    xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
    <wsp:Policy>
    <sp:TransportToken>
    <wsp:Policy>
    <sp:HttpsToken>
    <wsp:Policy />
    </sp:HttpsToken>
    </wsp:Policy>
    </sp:TransportToken>
    <sp:AlgorithmSuite>
    <wsp:Policy>
    <sp:Basic128 />
    </wsp:Policy>
    </sp:AlgorithmSuite>
    <sp:Layout>
    <wsp:Policy>
    <sp:Lax />
    </wsp:Policy>
    </sp:Layout>
    <sp:IncludeTimestamp />
    </wsp:Policy>
    </sp:TransportBinding>
    <sp:SignedSupportingTokens
    xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
    <wsp:Policy>
    <sp:SamlToken
    sp:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient">
    <wsp:Policy>
    <sp:WssSamlV20Token11 />
    </wsp:Policy>
    </sp:SamlToken>
    </wsp:Policy>
    </sp:SignedSupportingTokens>
    <sp:Wss11
    xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
    <wsp:Policy>
    <sp:MustSupportRefKeyIdentifier />
    <sp:MustSupportRefIssuerSerial />
    <sp:MustSupportRefThumbprint />
    <sp:MustSupportRefEncryptedKey />
    <sp:RequireSignatureConfirmation/>
    </wsp:Policy>
    </sp:Wss11>
    <sp:Trust13
    xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
    <wsp:Policy>
    <sp:MustSupportIssuedTokens />
    <sp:RequireClientEntropy />
    <sp:RequireServerEntropy />
    </wsp:Policy>
    </sp:Trust13>
    </wsp:All>
    </wsp:ExactlyOne>
    </wsp:Policy>

    ReplyDelete
  11. BTW, I've also tried with the policy included below. The problem for me is that it seems like the WS-SecurityPolicy allows you to specify that you require a SAML token and what version it must be. However, I can't see anything in the specification that describes how to require that the token is signed. Of course, this could be a failure on my part to grasp the finer details of the specification.

    <wsp:Policy wsu:Id="Transport_policy_saml">
    <wsp:ExactlyOne>
    <wsp:All>
    <wsap10:UsingAddressing />
    <sp:TransportBinding
    xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
    <wsp:Policy>
    <sp:TransportToken>
    <wsp:Policy>
    <sp:SamlToken sp:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient">
    <wsp:Policy>
    <sp:WssSamlV20Token11 />
    </wsp:Policy>
    </sp:SamlToken>
    </wsp:Policy>
    </sp:TransportToken>

    ReplyDelete
  12. Hi Colm,

    I think I solved my own problem. I perused the WS-SecurityPolicy specification and eventually tried adding this to my policy:

    <sp:SignedElements>
    <sp:XPath>//saml2:Assertion</sp:XPath>
    </sp:SignedElements>

    And it worked like a charm. So it looks like the issue here was really my incomplete understanding of WS-Policy.

    Still, I wonder if this is an example of INsecurity through obfuscation. This seems like a rather basic requirement to which the "correct" solution requires the understanding of a number of rather dense standards and frameworks. Wouldn't it be more secure to enforce signature of all elements by default and use the policy only to disable signing requirements?

    ReplyDelete
    Replies
    1. Hi Thomas,

      CXF accepts security tokens as "signed" in the context of SignedSupportingTokens if they are received over TLS OR if they are signed via XML Signature. It is somewhat of a grey area whether this is correct or not as the tokens are not directly signed. However it is up to the deployer to make sure that TLS is set up correctly with client authentication etc.

      Colm.

      Delete
    2. Well, anyway I spoke to soon. I thought the addition of the "SignedElements" in the policy would work. Instead, it turns out that the RST was failing for a different reason. It seems that if I add the "SignedElements" part in the policy, the SOAP body sent to the server is completely empty and as a result I get:

      "No binding operation info while invoking unknown method with params unknown."

      Any thoughts?

      Delete
    3. This comment has been removed by the author.

      Delete
    4. This comment has been removed by the author.

      Delete
    5. Sorry, my brain is fried from staring at code. There are two things I wanted to mention:

      1. The STS does not properly create its WSDL when I include the "SignedElements" policy. Thus the STSClient is not able to properly create the BindingOperationInfo and therefore the request fails with "No binding operation info..."

      2. In regard to your previous comment. Even with proper TLS client authentication, this means that any client that authenticates with a certificate trusted by the server can impersonate any user. Because it does not seem to me that the code is comparing the subject of the client certificate to the issuer or subject of the SAML token.

      Thus, this is still a security hole as far as I can tell because it means that with successful TLS client authentication I can submit an unsigned SAML token with any subject and issuer information.

      Delete
  13. In conclusion.
    1. The issue I was having with the inclusion of "SignedElements" was that I was not propery specifying the namespace. In order to specify this you need:
    <sp:SignedElements
    xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
    <sp:XPath>//*[name()='saml2:Assertion']</sp:XPath>
    </sp:SignedElements>

    HOWEVER, that didn't work anyway because the WSS4J SamlAssertionValidator marks the SAML assertion as a unsigned supporting token (WSConstants.ST_UNSIGNED) and the CXF PolicyBasedWSS4JInInterceptor does not take these into account when verifying "SignedElements"

    2. I did figure out one way to make CXF check the SAML signature by including a "SignedSupportingTokens" policy in the "TransportBinding" policy:

    <sp:TransportBinding
    xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
    <wsp:Policy>
    <sp:TransportToken>
    <wsp:Policy>
    <sp:SamlToken sp:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient">
    <wsp:Policy>
    <sp:WssSamlV20Token11 />
    </wsp:Policy>
    </sp:SamlToken>
    </wsp:Policy>
    </sp:TransportToken>
    … stuff here omitted for brevity …
    <sp:SignedSupportingTokens
    xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
    <wsp:Policy>
    <sp:SamlToken
    sp:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient">
    <wsp:Policy>
    <sp:WssSamlV20Token11 />
    </wsp:Policy>
    </sp:SamlToken>
    </wsp:Policy>
    </sp:SignedSupportingTokens>
    </wsp:Policy>
    </sp:TransportBinding>

    HOWEVER, while that forces CXF to check that the SAML assertion is signed, CXF ignores this verification because I am using TLS. Futhermore, CXF doesn't even care if I use a client certificate or not!

    Here's the offending code from org.apache.cxf.ws.security.wss4j.policyvalidators.AbstractSupportingTokenPolicyValidator:

    private boolean isTLSInUse() {
    // See whether TLS is in use or not
    TLSSessionInfo tlsInfo = message.get(TLSSessionInfo.class);
    if (tlsInfo != null) {
    return true;
    }
    return false;
    }

    I would recommend, at the very least, that the code be modified to:

    private boolean isTLSInUse() {
    // See whether TLS is in use or not
    TLSSessionInfo tlsInfo = message.get(TLSSessionInfo.class);
    if ( (tlsInfo != null) && (tlsInfo.getPeerCertificates() != null) ) {
    return true;
    }
    return false;
    }

    However, that still allows anyone with a valid certificate to essentially impersonate any user.

    ReplyDelete
  14. For the record, I've fixed the first issue "SignedElements" in WSS4J and tested that it's working with the latest CXF/WSS4J SNAPSHOTS: https://git-wip-us.apache.org/repos/asf?p=cxf.git;a=commit;h=b7a7916a3095578b70a6b9d9e8d0bd7a94e4d051

    I'll respond to your second point via a different channel...

    Colm.

    ReplyDelete
  15. Thanks Colm. Your help with these issues is very much appreciated.

    ReplyDelete