Friday, November 4, 2011

Apache CXF STS documentation - part VI

In the previous post, I covered the TokenValidator interface that is used by the CXF STS to validate tokens, and two implementations that ship with the STS to validate SecurityContextTokens and BinarySecurityTokens (X509 Certificates). In this post I will cover the other two TokenValidator implementations that ship with the STS, which can validate UsernameTokens and SAML Tokens (both 1.1 and 2.0).

1) The UsernameTokenValidator

The UsernameTokenValidator is used to validate WS-Security UsernameTokens. Two properties can be set directly on the UsernameTokenValidator:
  • void setValidator(Validator validator) - Set the WSS4J Validator instance to use to validate the received UsernameToken. The default is the UsernameTokenValidator (note that this is in WSS4J and not the same as the UsernameTokenValidator in the STS!).
  • void setUsernameTokenRealmCodec(UsernameTokenRealmCodec usernameTokenRealmCodec) - Set the UsernameTokenRealmCodec instance to use to return a realm from a validated token. This will be explained later.
The UsernameToken is first checked to make sure that it is well-formed. If it has no password element then it is rejected. If a cache is configured, then it sees if the UsernameToken has been previously stored in the cache (searching by wsu:Id). If it is, and if the retrieved SecurityToken has an "associated hash" corresponding to the hashcode of the received UsernameToken, then the received UsernameToken has already been validated, and no further validation is required. It is necessary to check the "associated hash" value, in case two different UsernameTokens are being compared that have the same wsu:Id. Note that the CXF STS does not have a UsernameTokenProvider as of yet, so for this use-case perhaps the cache is shared with a custom TokenProvider.

If the token is not stored in the cache, then the WSS4J Validator instance is used to validate the received UsernameToken. As stated above, the default implementation that is used is the UsernameTokenValidator in WSS4J. This implementation uses a CallbackHandler to supply a password to validate the UsernameToken. This CallbackHandler implementation is supplied by the STSPropertiesMBean object. WSS4J also ships with an implementation that validates a UsernameToken via a JAAS LoginModule, which can be plugged in to the STS UsernameTokenValidator. If validation is successful, then a principal is created from the received UsernameToken and set on the response.

2) Realms in the TokenValidators

Recall that the TokenValidator interface has a method that takes a realm parameter:
  • boolean canHandleToken(ReceivedToken validateTarget, String realm) - Whether this TokenValidator implementation can validate the given token in the given realm
How the STS knows what the desired realm is will be covered in a future post.  
Realms are handled in a slightly different way in TokenValidators compared to TokenProviders. Recall that for TokenProviders, the implementation is essentially asked whether it can provide a token in a given realm. For the SCTProvider, the realm is ignored in this method. However, when creating a token, the SCTProvider will store the given realm as a property associated with that token in the cache. The SAMLTokenProvider checks to see if the given realm is null, and if it is not null then the realmMap *must* contain a key which matches the given realm.

There is a subtle distinction between the realm passed to "canHandleToken" for TokenValidators and the realm returned after a token is validated as part of the TokenValidatorResponse object. The realm passed to "canHandleToken" is the realm to validate the token in. So for example, you could have two TokenValidator instances registered to validate the same token, but in different realms. All of the TokenValidator implementations that ship with the STS ignore the realm as part of this method. However, the method signature gives the user the option to validate tokens in different realms in a more flexible manner.

The realm that is returned as part of the TokenValidatorResponse is the realm that the validated token is in (if any). This can be different to the realm the token was validated in. The X509TokenValidator ignores this parameter altogether. The SCTValidator checks to see whether the SecurityToken that was stored in the cache has a realm property, and if so sets this on the TokenValidatorResonse. The UsernameTokenValidator and SAMLTokenValidator handle realms in a more sophisticated manner. I will cover the UsernameTokenValidator here, and the SAMLTokenValidator later. Recall that the UsernameTokenValidator has the following method:
  • void setUsernameTokenRealmCodec(UsernameTokenRealmCodec usernameTokenRealmCodec) - Set the UsernameTokenRealmCodec instance to use to return a realm from a validated token. 
The UsernameTokenRealmCodec has a single method:
  • String getRealmFromToken(UsernameToken usernameToken) - Get the realm associated with the UsernameToken parameter.
No UsernameTokenRealmCodec implementation is set by default on the UsernameTokenValidator, hence no realm is returned in TokenValidatorResponse. If an implemention is specified, then the UsernameTokenValidator will retrieve a realm from the UsernameTokenRealmCodec implementation corresponding to the validated UsernameToken. If a cache is configured, and the UsernameToken was already stored in the cache, then the realm is compared to the realm of the cached token, stored under the tag "org.apache.cxf.sts.token.realm". If they do not match then validation fails.

3) The SAMLTokenValidator

The SAMLTokenValidator is used to validate SAML (1.1 and 2.0) tokens. The following properties can be set directly on the SAMLTokenValidator:
  • void setValidator(Validator validator) - Set the WSS4J Validator instance to use to validate the received certificate. The default is SignatureTrustValidator.
  • void setSamlRealmCodec(SAMLRealmCodec samlRealmCodec) - Set the SAMLRealmCodec instance to use to return a realm from a validated token.
  • void setSubjectConstraints(List<String> subjectConstraints) - Set a list of Strings corresponding to regular expression constraints on the subject DN of a certificate that was used to sign an Assertion.
These methods are covered in more detail below. The Assertion is first checked to make sure that it is well-formed. If a cache is defined, then the signature value of the received assertion (if it is signed) is hashed and compared against the tokens in the cache (via getTokenByAssociatedHash()). If a match is found in the cache, then the Assertion is taken to be valid. If a match is not found, then the Assertion is validated.
3.1) Validating a received SAML Assertion

If the token is not stored in the cache then it must be validated. Firstly a check is performed to make sure that the Assertion is signed, if it is not then it is rejected. The signature of the Assertion is then validated using the Crypto object retrieved from the STSPropertiesMBean passed in the TokenValidatorParameters. Finally, trust is verified in the certificate/public-key used to sign the Assertion. This is done using the Validator object that can be configured via "setValidator". The default Validator is the WSS4J SignatureTrustValidator, which checks that the received certificate is known (or trusted) by the STS Crypto object.

Recall that a List of Strings can be set on the SAMLTokenValidator via the "setSubjectConstraints" method. These Strings correspond to regular expression constraints on the subject DN of a certificate that was used to sign an Assertion. This provides additional flexibility to validate a received SAML Assertion. For example, the Assertion could be signed by an entity that has a certificate issued by a particular CA, which in turn is trusted by the STS Crypto object. However, one might want to restrict the list of "valid" entities who can sign a SAML Assertion. This can be done by adding a list of regular expressions that match the Subject DN of all acceptable certificates that might be used to sign a valid SAML Assertion. This matching is done by the CertConstraintsParser.

3.2) Realm handling in the SAMLTokenValidator

Recall that the SAMLTokenValidator has the following method:
  • void setSamlRealmCodec(SAMLRealmCodec samlRealmCodec) - Set the SAMLRealmCodec instance to use to return a realm from a validated token.
The SAMLRealmCodec has a single method:
  • String getRealmFromToken(AssertionWrapper assertion) - Get the realm associated with the (SAML Assertion) parameter.
No SAMLRealmCodec implementation is set by default on the SAMLTokenValidator, hence no realm is returned in TokenValidatorResponse. If an implemention is specified, then the SAMLTokenValidator will retrieve a realm from theSAMLRealmCodec  implementation corresponding to the validated Assertion. If a cache is configured, and the Assertion was already stored in the cache, then the realm is compared to the realm of the cached token, stored under the tag "org.apache.cxf.sts.token.realm". If they do not match then validation fails.

7 comments:

  1. I am getting the following error while trying to have CXF consume a SAML token. What could be the cause of this error?

    Caused by: org.apache.ws.security.WSSecurityException: General security error (Provided SAML token does not contain a suitable key)
    at org.apache.ws.security.validate.SamlAssertionValidator.validate(SamlAssertionValidator.java:61)
    at org.apache.ws.security.processor.SAMLTokenProcessor.handleSAMLToken(SAMLTokenProcessor.java:118)
    at org.apache.ws.security.processor.SAMLTokenProcessor.handleToken(SAMLTokenProcessor.java:53)
    at org.apache.ws.security.WSSecurityEngine.processSecurityHeader(WSSecurityEngine.java:396)
    at org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor.handleMessage(WSS4JInInterceptor.java:249)

    By the way, I am using CXF 2.5.0 for the server while the client is .NET.

    ReplyDelete
  2. Hi Dan,

    I already answered your question on dev@cxf here:

    http://cxf.547215.n5.nabble.com/Re-General-security-error-Provided-SAML-token-does-not-contain-a-suitable-key-td4990489.html

    Colm.

    ReplyDelete
  3. Thanks. I replied to the cxf-dev forum post.

    ReplyDelete
  4. Hi,
    I am sending a username token and then validating the username token. On validation of user name token STS provides me SAML token. everything is working fine but i am not able to see SAML token in my response from STS . However i am able to see username token. I want to see SAML token. Please help.

    ReplyDelete
  5. Hi,

    I suggest posting a more in-depth explanation of your use-case to the CXF users mailing list.

    Colm.

    ReplyDelete
  6. Colm:

    I have a similar issue (as above). I am using PicketLinkSTS to issue a token for a secure service (that is using MEX tag). The problem appears to be inside the Secure Service, at the point the token is issued and returned from the STS. I stepped through the classes and they match up with your explanation above. However I am not sure why I'd get the following exception

    Caused by: org.apache.ws.security.WSSecurityException: General security error (Unable to load class org.apache.ws.security.validate.SamlAssertionValidator)
    at org.apache.ws.security.WSSConfig.getValidator(WSSConfig.java:765) [wss4j-1.6.5.jar:1.6.5]
    at org.apache.ws.security.handler.RequestData.getValidator(RequestData.java:451) [wss4j-1.6.5.jar:1.6.5]
    at org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor$CXFRequestData.getValidator(WSS4JInInterceptor.java:692) [cxf-rt-ws-security-2.4.6.jar:2.4.6]
    at org.apache.ws.security.processor.SAMLTokenProcessor.handleToken(SAMLTokenProcessor.java:51) [wss4j-1.6.5.jar:1.6.5]
    at org.apache.ws.security.WSSecurityEngine.processSecurityHeader(WSSecurityEngine.java:397) [wss4j-1.6.5.jar:1.6.5]
    at org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor.handleMessage(WSS4JInInterceptor.java:249) [cxf-rt-ws-security-2.4.6.jar:2.4.6]
    ... 28 more
    Caused by: java.lang.NoClassDefFoundError: org/opensaml/xml/validation/ValidationException
    at java.lang.Class.getDeclaredConstructors0(Native Method) [rt.jar:1.7.0_21]
    at java.lang.Class.privateGetDeclaredConstructors(Class.java:2413) [rt.jar:1.7.0_21]
    at java.lang.Class.getConstructor0(Class.java:2723) [rt.jar:1.7.0_21]
    at java.lang.Class.newInstance0(Class.java:345) [rt.jar:1.7.0_21]
    at java.lang.Class.newInstance(Class.java:327) [rt.jar:1.7.0_21]
    at org.apache.ws.security.WSSConfig.getValidator(WSSConfig.java:760) [wss4j-1.6.5.jar:1.6.5]



    ReplyDelete
  7. I've been pointed to the last comment by Colm; if the stacktrace is from JBoss AS 7, I basically suggest upgrading to a newer version / to WildFly, basically because at the time Apache CXF 2.4.x was used in the JBoss integration with CXF, the SAML and WS-Trust functionalities were not tested / working.

    Btw, I believe this is related to the JBoss forum thread I just comment on at https://community.jboss.org/message/844345#844345

    ReplyDelete