An inbound security header is processed by WSS4J by iterating through each child element of the header, and by calling the appropriate Processor implementation to deal with each element. In WSS4J 1.5.x, some processors perform validation on the received token (e.g. UsernameTokens), whereas others store the processing results for later verification by third-party WS-Handler implementations (e.g. Timestamp verification, Certificate trust verification). There are some problems with this approach:
- It is not consistent, some processors perform validation, others do not.
- There is a potential security hole, in that it is assumed third-party code will know to validate the credentials that the WSS4J processors do not validate.
- WSS4J will continue to process the rest of the security header even if the Timestamp is invalid, or the certificate non-trusted, which could lead to denial-of-service attacks.
- There is no separation of concerns between processing the token and validating the token. If you want to change how the token is validated, you must replace the processor instance.
In WSS4J 1.5.x, CallbackHandler implementations are used in different ways by different processors, sometimes they are expected to verify a password (as for processing UsernameTokens), and other times they are expected to supply a password (as for decryption). In WSS4J 1.6, CallbackHandler implementations are only expected to supply a password (if it exists) to the processors. The Processor implementations do not perform any validation of the security token, instead they package up the processed token, along with any (password) information extracted from the CallbackHandler, and hand it off to a Validator implementation for Validation.
The Processor implementations get the specific Validator implementation to use via the RequestData parameter, which in turn asks a WSSConfig object for the Validator implementation. If the Validator is null, then no Validation is performed on the received token. The Processor then stores the received token as normal. WSS4J 1.6 comes with several default Validators, which are:
- NoOpValidator: Does no processing of the credential
- TimestampValidator: Validates a Timestamp
- UsernameTokenValidator: Validates a UsernameToken
- SignatureTrustValidator: Verifies trust in a signature
- SamlAssertionValidator: Checks some HOK requirements on a SAML Assertion, and verifies trust on the (enveloped) signature.
- TAG_VALIDATED_TOKEN: Indicates that the token corresponding to this result has been validated by a Validator implementation. Some of the processors do not have a default Validator implementation.
- TAG_TRANSFORMED_TOKEN: A Validator implementation may transform a credential (into a SAML Assertion) as a result of Validation. This tag holds a reference to an AssertionWrapper instance, that represents a transformed version of the validated credential.
An example of how to add a custom Validator implementation is the STSTokenValidator in CXF 2.4.0. The STSTokenValidator tries to validate a received SAML Assertion locally, and if that fails, it dispatches it to a Security Token Service (STS) via the WS-Trust interface for validation. It also supports validating a UsernameToken and BinarySecurityToken in the same manner. The SecurityConstants class defines some configuration tags for specifying a custom validator for inbound SAML1, SAML2, UsernameToken, BinarySecurityToken, Signature and Timestamps. The STSTokenValidator can be configured by associating it with the appropriate configuration tag.
I have to accept UsernameToken with plaintext password for a web service and authenticate against another data store... It took me a while, but I figured out how to register my custom validator using the CXF jaxws Spring namespace:
ReplyDelete<jaxws:endpoint id="myServiceEndpoint" ... >
<jaxws:inInterceptors>
<bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
<constructor-arg>
<map>
<entry key="#{T(org.apache.ws.security.handler.WSHandlerConstants).ACTION}" value="#{T(org.apache.ws.security.WSConstants).USERNAME_TOKEN_LN}" />
<entry key="#{T(org.apache.ws.security.handler.WSHandlerConstants).PASSWORD_TYPE}" value="#{T(org.apache.ws.security.WSConstants).PW_TEXT}" />
<entry key="#{T(org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor).VALIDATOR_MAP}">
<map>
<entry key="#{T(org.apache.ws.security.WSSecurityEngine).USERNAME_TOKEN}">
<bean class="com.mycompany.wss4j.MyCustomUsernameTokenValidator" />
</entry>
</map>
</entry>
</map>
</constructor-arg>
</bean>
</jaxws:inInterceptors>
</jaxws:endpoint>
Hi Patrick,
ReplyDeleteI will post a blog entry soon explaining more about how to use WSS4J Validators in CXF. In the meantime, it is possible to configure it for your case in a simpler way than modifying the VALIDATOR_MAP variable. Just set the following jaxws property "ws-security.ut.validator" to your custom validator:
http://cxf.apache.org/javadoc/latest/org/apache/cxf/ws/security/SecurityConstants.html#USERNAME_TOKEN_VALIDATOR
Colm.
Hi Patrick,
ReplyDeleteplease can you suggest any example how to set it up without CXF, preferable within Spring WS.
Thank you!
Best regards,
Andrej
Colm,
ReplyDeleteHow can we implement UsernameToken with plaintext password for Axis2 1.6.2 version?
Thank you
Anita
I suggest asking the Rampart users list.
ReplyDeleteColm.
This comment has been removed by the author.
ReplyDeleteHi, thanks for your aportation. Share with you my necesary changes for migrating to cxf 2.5.3 and wss4j 1.6.x:
ReplyDelete**ServerWSS4JInInterceptor.java
public class ServerWSS4JInInterceptor extends WSS4JInInterceptor {
static Map map;
static {
map = new HashMap();
map.put(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
map.put(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PASSWORD_TEXT); // No usar: WSConstants.PW_TEXT);
map.put(WSHandlerConstants.PW_CALLBACK_REF, new AuthenticationTokenCallbackHandler());
//Cambios necesarios que funcione con el nuevo WSS4J 1.6.x:
map.put(WSHandlerConstants.IS_BSP_COMPLIANT, "false");
final Map customMap = new HashMap();
CustomUsernameTokenValidator validator = new CustomUsernameTokenValidator();
customMap.put(WSSecurityEngine.USERNAME_TOKEN, validator);
map.put(WSS4JInInterceptor.VALIDATOR_MAP, customMap);
}
public ServerWSS4JInInterceptor() {
super(map);
}
}
** CustomUsernameTokenValidator.java
public class CustomUsernameTokenValidator extends UsernameTokenValidator implements Validator {
@Override
protected void verifyDigestPassword(UsernameToken usernameToken,
RequestData data) throws WSSecurityException {
...
//CORRECCIÓN AQUÍ: PARA QUE ARRASTRE LA CLAVE HACIA EL CALLBACK
// WSPasswordCallback pwCb =
// new WSPasswordCallback(user, null, pwType, WSPasswordCallback.USERNAME_TOKEN, data);
WSPasswordCallback pwCb =
new WSPasswordCallback(user, password, pwType, WSPasswordCallback.USERNAME_TOKEN, data);
...
}
** AuthenticationTokenCallbackHandler.java
public class AuthenticationTokenCallbackHandler implements CallbackHandler {
private static org.apache.commons.logging.Log log =
org.apache.commons.logging.LogFactory.getLog(AuthenticationTokenCallbackHandler.class);
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException
{
WSPasswordCallback pc = null;
for (Callback callback : callbacks) {
if (callback instanceof WSPasswordCallback) {
pc = (WSPasswordCallback) callback;
break;
}
}
if ((WSPasswordCallback.USERNAME_TOKEN_UNKNOWN != pc.getUsage())
&& (WSPasswordCallback.USERNAME_TOKEN != pc.getUsage())){
throw new SecurityException("Only 'UsernameToken' is suported.");
}
//Nota:
//En este punto se podría verificar la password, aunque WS-Security 1.6.x indica
//que debe realizarse en CustomUsernameTokenValidator
log.debug("Usuario recibido : " + pc.getIdentifier() +
" con uso " + pc.getUsage());
//+ " y pww="+pc.getPassword()
}
}
how to verify the signed soap message using org.apache.ws.security wss4j jar ?
ReplyDeleteCan anyone help me with the sample code.