Friday, February 4, 2011

UsernameToken processing changes in WSS4J 1.6

The forthcoming WSS4J 1.6 release contains some significant changes relating to how wsse UsernameTokens are processed. In WSS4J 1.5.x, the following processing rules applied:
  • For a digest password, the CallbackHandler implementation was given the username and an identifier of WSPasswordCallback.USERNAME_TOKEN. It was then expected to set the password on the callback, and the processor did the comparison.
  • For a plaintext password, the CallbackHandler implementation was given the username, password, and an identifier of WSPasswordCallback.USERNAME_TOKEN_UNKNOWN, and was expected to do all validation of the plaintext password itself, throwing an exception if validation failed.
  • For a password of some unspecified non-standard type, WSS4J would throw an exception by default. However, if wssConfig.getHandleCustomPasswordTypes() returned true, then it would again dispatch the username, password, and an identifier of WSPasswordCallback.USERNAME_TOKEN_UNKNOWN to the CallbackHandler implementation for validation.
  • For the case of a UsernameToken with no password element, it would again dispatch the username, password, and an identifier of WSPasswordCallback.USERNAME_TOKEN_UNKNOWN to the CallbackHandler implementation for validation.
The reason that the processor does not validate the plaintext password, as per the digest case, is to accommodate dispatching the username/password to (for example) a directory store for authentication. The custom password type is treated as a separate case namely for WCF interoperability (see here).

There are a couple of several obvious problems with this set-up:
  • The standard plaintext password case is treated exactly the same as a non-standard password type, or the case of no password at all, by sending the callback handler a type of WSPasswordCallback.USERNAME_TOKEN_UNKNOWN.
  • The treatment of passwords for both the standard digest and plaintext cases are inconsistent.
  • A potential security hole exists where the user may not be aware that the CallbackHandler implementation *must* throw an exception on WSPasswordCallback.USERNAME_TOKEN_UNKNOWN when the password is not recognised.
  • The CallbackHandler interface is being used in a non-standard way - it is only meant to supply a password, not do the validation.
  • UsernameTokens with no password (i.e. used for key derivation) are stored using the same result (WSConstants.UT) as UsernameTokens that have been validated. This could lead to a security hole where a user assumes that because a UsernameToken has been processed, password validation has taken place.
WSS4J 1.6 fixes these issues, at the cost of breaking backwards compatibility in terms of the semantics of the CallbackHandler implementation. The UsernameTokenProcessor now does no validation of the password, beyond making sure it is well formed. A new Validator implementation (more on this at a later date) does the validation of the token. The default behaviour is as follows:
  • For the digest case, the CallbackHandler is given the username, password type and an identifier of WSPasswordCallback.USERNAME_TOKEN. It must set the password on the callback, and the validator does the comparison. This is the same as the old behaviour.
  • The plaintext case has exactly the same behaviour as the digest case. The identifier is now WSPasswordCallback.USERNAME_TOKEN and not WSPasswordCallback.USERNAME_TOKEN_UNKNOWN, and the CallbackHandler does not do any authentication, but must set the password on the callback.
  • The custom password type case defaults to the same behaviour as the plaintext case, assuming wssConfig.getHandleCustomPasswordTypes() returns true.
  • For the case of a username token with no password element, the default behaviour is simply to ignore it, and to store it as a new result of type WSConstants.UT_NOPASSWORD.
So we now have the correct use of the CallbackHandler interface - it does no authentication, and is only used to retrieve password for validation. The plaintext and digest cases are treated the same way by default. There is no security hole in that the user does not need to throw exceptions in the CallbackHandler implementation for various cases. Also, the case of no password at all is not validated, but is stored as a separate type, for use by other processors to derive keys for decryption or signature verification.

So what if you want to validate the plaintext password against a directory store, rather than have the CallbackHandler set the password? Instead of implementing this behaviour in your CallbackHandler implementation, you can simply @Override the verifyPlaintextPassword(UsernameToken usernameToken) method in the validator instead. By simply plugging in a validator on the UsernameTokenProcessor (such as the NoOpValidator), it is possible to do any kind of custom validation (or none at all) on the token. This is a much better solution than having to write a custom processor, and replace the existing UsernameTokenProcessor.

13 comments:

  1. I got stuck when using WSS4J in Spring WS.
    Signature validation is working, but there seems to be no CallbackHandler for delegating authentication to LDAP (like in XWSS): see http://static.springsource.org/spring-ws/sites/2.0/reference/html/security.html#security-certificate-authentication

    In JIRA (https://issues.apache.org/jira/browse/WSS-266) you mentioned something about using a customized UsernameTokenValidator... could you please provide an example of how to do this?

    How to hook this in into the Wss4jSecurityInterceptor?

    <bean
    class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="validationActions" value="Signature" />
    <property name="validationSignatureCrypto" ref="serverValidationCrypto" />
    <property name="enableSignatureConfirmation" value="true" />
    <property name="validationCallbackHandlers">
    <list>
    <ref bean="certificateValidationHandler" />
    </list>
    </property>
    </bean>

    ReplyDelete
  2. What version of WSS4J are you using? The validator stuff only applies to 1.6.0.

    Colm.

    ReplyDelete
  3. Hi pixotec,

    I believe you are using the wss4j1.6.0 all you have to do in your bean delineation is add a explicit reference for the callback handler class which needs to be initialized with the user name/user alias and password.

    Have fun!!

    ReplyDelete
  4. Hi,

    I did not find any documentation on how to plug in custom validator. For example, how do I make CXF use NoOpValidator? I am using Apache CXF 2.4.0. In my case, I want to supply them programmatically.

    ReplyDelete
  5. To see how to configure a Validator implementation programatically in CXF 2.4.0 take a look at the SAMLTokenTest:

    http://svn.apache.org/viewvc/cxf/tags/cxf-2.4.0/rt/ws/security/src/test/java/org/apache/cxf/ws/security/wss4j/saml/SamlTokenTest.java?view=markup

    final Map customMap = new HashMap();
    CustomSamlValidator validator = new CustomSamlValidator();
    customMap.put(WSSecurityEngine.SAML_TOKEN, validator);
    customMap.put(WSSecurityEngine.SAML2_TOKEN, validator);
    inProperties.put(WSS4JInInterceptor.VALIDATOR_MAP, customMap);

    Colm.

    ReplyDelete
  6. It solves my problem. Thank you very much.

    ReplyDelete
  7. I posted a comment with a Spring XML example of using a custom Validator here: http://coheigea.blogspot.com/2011/04/wss4j-16-introducing-validators.html

    ReplyDelete
  8. Hi Colm,
    These look like really worthwhile changes. However, I'm having a problem. I don't want to use a password but I don't know what to set the action to in my Spring Bean config. If I set it to "Username", then I get an 'actions don't match' error down the line because when the SOAP is received without a password the integer representation is set to 8192 (UT_NOPASSWORD) where as the action list is still set to 1 (UT). There does not appear to be a corresponding text representation of UT_NOPASSWORD.
    Hoping you can help.

    ReplyDelete
  9. For the record, this task was addressed by:

    https://issues.apache.org/jira/browse/WSS-321

    Colm.

    ReplyDelete
  10. Hi Colm,
    I ran into the same problem as Paul R. above. Finally tracked it into the Processor that Paul found which taught me enough to make the right search to find this :-) Just wanted to say THANK YOU for providing the fix to the NoPassword problem. Was not looking forward to hacking around with the Processor and maintaining a duplicate code line!
    thanks,
    Jesse

    ReplyDelete
  11. do any one have a working sample ?(for learn)

    ReplyDelete
  12. so you are expecting us to keep clean text passwords ??? What is the point of such implementation ?

    ReplyDelete
  13. You can alternatively use the JAASUsernameTokenValidator to validate the password against an LDAP backend.

    ReplyDelete