Affects: spring-ws-security-3.0.8.RELEASE


Description

I have spring web services deployed to remote hosts offshore that may not be using NTP services to keep their clocks synchronised resulting in potential skewing of those server system clocks. This is out of my control and causes an issue with the web service ws-security handling specifically relating to the ws-security header timestamp validation of the received message.

I attempted to increase the time to live attributes on the definition of the Wss4jSecurityInterceptor bean for example by setting these 3:

      <property name="timestampStrict" value="${server.wss.timestampStrict}" />
      <property name="validationTimeToLive" value="${server.wss.validation.ttl}" />
      <property name="futureTimeToLive" value="${server.wss.future.ttl}" />

The full bean definition:

<bean id= "wss4jInterceptor" class="org.springframework.ws.soap.security.wss4j2.Wss4jSecurityInterceptor">
      <!-- Securing the outgoing message -->
      <property name="securementActions" value="${server.wss.actions}" />
      <property name="timestampPrecisionInMilliseconds" value="true" />
      <property name="securementTimeToLive" value="${server.wss.securement.ttl}" />
      <property name="securementSignatureKeyIdentifier" value="DirectReference" />
      <property name="securementUsername" value="${ws.server.ks.sig.reply.privatekey.alias}" />
      <property name="securementPassword" value="${ws.server.ks.sig.reply.privatekey.password}" />
      <property name="securementSignatureParts" value="{}{http://schemas.xmlsoap.org/soap/envelope/}Body" />
      <property name="securementSignatureCrypto">
        <bean class="org.springframework.ws.soap.security.wss4j2.support.CryptoFactoryBean">
          <property name="keyStorePassword" value="${ws.server.ks.sig.reply.password}" />
          <property name="keyStoreLocation" value="${ws.server.ks.sig.reply.url}" />
        </bean>
      </property>
      <!-- Validating the incoming message -->
      <property name="validationActions" value="${server.wss.actions}" />
      <property name="timestampStrict" value="${server.wss.timestampStrict}" />
      <property name="validationTimeToLive" value="${server.wss.validation.ttl}" />
      <property name="futureTimeToLive" value="${server.wss.future.ttl}" />
      <property name="validationSignatureCrypto">
        <bean class="org.springframework.ws.soap.security.wss4j2.support.CryptoFactoryBean">
          <property name="keyStorePassword" value="${server.ks.sig.rqst.password}" />
          <property name="keyStoreLocation" value="${server.ks.sig.rqst.url}" />
        </bean>
      </property>
      <property name="exceptionResolver" ref="myExceptionResolver"/>
    </bean>

Problem

Strangely, the problem did not improve. The timestamp validation was still failing. I had increased the ttl values to 420 seconds (7 minutes) but it appeared the update had no effect. I've done some analysis debugging the Wss4jSecurityInterceptor locally and came up with the following observations: Default values from Wss4jSecurityInterceptor:

private boolean timestampStrict = true;
private int validationTimeToLive = 300;
private int futureTimeToLive = 60;

The TimestampValidator.validate() method is actually called twice within the Wss4jSecurityInterceptor.validateMessage() method.
Firstly in the securityEngine.processSecurityHeader() and subsequently in the verifyTimestamp() method. It appears that the TimestampValidator.validate() method requires an initialised RequestData object passed in with the various time to live attributes propagated from the attributes of the Wss4jSecurityInterceptor.

Within the code of the Wss4jSecurityInterceptor.validateMessage() method there is an attempt to do just that via initializeValidationRequestData():

@Override
protected void validateMessage(SoapMessage soapMessage, MessageContext messageContext)
            throws WsSecurityValidationException {
...
RequestData validationData = initializeValidationRequestData(messageContext);
WSHandlerResult result = securityEngine
                .processSecurityHeader(elem, validationData);
...
verifyTimestamp(result);

However, the initializeValidationRequestData() method doesn't propagate the attributes. Therefore the processSecurityHeader() processing ends up using the default values contained in the RequestData object being:

    /**
     * The time in seconds between creation and expiry for a Timestamp. The default
     * is 300 seconds (5 minutes).
     */
    private int timeStampTTL = 300;

    /**
     * The time in seconds in the future within which the Created time of an incoming
     * Timestamp is valid. The default is 60 seconds.
     */
    private int timeStampFutureTTL = 60;

This is why the overall ws-security header validation still fails because it's using these default values instead of those I had explicitly set on the Wss4jSecurityInterceptor bean.

Workaround

To address this, I extended Wss4jSecurityInterceptor and overrode the initializeValidationRequestData() method as follows:

    /**
     * Fix: The parent calls a processSecurityHeader method on the <code>WSSecurityEngine</code>
     * prior to calling the verifyTimestamp as part of the validateMessage method.
     * The problem is that the necessary time to live attributes are not propagated 
     * during the initializeValidationRequestData method in the parent so we need
     * to do it explicitly here unfortunately.
     */
    @Override
    protected RequestData initializeValidationRequestData(MessageContext messageContext)
    {
        RequestData requestData = new RequestData();
        requestData = super.initializeValidationRequestData(messageContext);
        requestData.setTimeStampFutureTTL(futureTimeToLive);
        requestData.setTimeStampTTL(validationTimeToLive);
        requestData.setTimeStampStrict(timestampStrict);
        return requestData;
    }

This ensures that the RequestData object is correctly configured before either of the two calls to verify the timestamps. Without this, the processSecurityHeader() would fail the timestamp validation as it wasn't picking up the specified ttl values from the bean.

I think the initializeValidationRequestData() method in Wss4jSecurityInterceptor class should propagate the values to the RequestData object as per my overridden method and that would solve this bug.

Perhaps then the correctly initialised RequestData object could be passed to the verifyTimestamp() method too precluding the need for a new object to be created again inside that method.

Comment From: rstoyanchev

@mightybeaker I believe this issue is intended for the issue tracker of the Spring Web Services. Please see the link to the JIRA issue tracker on that page and create the same issue there instead.

If I am mistaken however, and you have reason to believe the issue is in the Spring Framework, then please explain, and I will re-open again.

Comment From: mightybeaker

@rstoyanchev apologies and thank you for the advice. Having joined only yesterday I wasn't aware of the separate issue tracking for spring-ws. I've created issue https://jira.spring.io/browse/SWS-1084