You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ws.apache.org by co...@apache.org on 2013/09/20 12:41:37 UTC

svn commit: r1524969 - in /webservices/wss4j/trunk: ws-security-common/src/main/java/org/apache/wss4j/common/ ws-security-common/src/main/resources/messages/ ws-security-dom/src/main/java/org/apache/wss4j/dom/handler/ ws-security-dom/src/main/java/org/...

Author: coheigea
Date: Fri Sep 20 10:41:37 2013
New Revision: 1524969

URL: http://svn.apache.org/r1524969
Log:
[WSS-478] - Support the ability to cache SAML2 Assertions with "OneTimeUse" Conditions

Modified:
    webservices/wss4j/trunk/ws-security-common/src/main/java/org/apache/wss4j/common/ConfigurationConstants.java
    webservices/wss4j/trunk/ws-security-common/src/main/resources/messages/wss4j_errors.properties
    webservices/wss4j/trunk/ws-security-dom/src/main/java/org/apache/wss4j/dom/handler/RequestData.java
    webservices/wss4j/trunk/ws-security-dom/src/main/java/org/apache/wss4j/dom/processor/SignatureProcessor.java
    webservices/wss4j/trunk/ws-security-dom/src/main/java/org/apache/wss4j/dom/validate/SamlAssertionValidator.java
    webservices/wss4j/trunk/ws-security-dom/src/test/java/org/apache/wss4j/dom/message/ReplayTest.java
    webservices/wss4j/trunk/ws-security-stax/src/main/java/org/apache/wss4j/stax/ConfigurationConverter.java
    webservices/wss4j/trunk/ws-security-stax/src/main/java/org/apache/wss4j/stax/ext/WSSSecurityProperties.java
    webservices/wss4j/trunk/ws-security-stax/src/main/java/org/apache/wss4j/stax/impl/processor/input/WSSSignatureReferenceVerifyInputProcessor.java
    webservices/wss4j/trunk/ws-security-stax/src/main/java/org/apache/wss4j/stax/validate/SamlTokenValidatorImpl.java
    webservices/wss4j/trunk/ws-security-stax/src/test/java/org/apache/wss4j/stax/test/ReplayTest.java

Modified: webservices/wss4j/trunk/ws-security-common/src/main/java/org/apache/wss4j/common/ConfigurationConstants.java
URL: http://svn.apache.org/viewvc/webservices/wss4j/trunk/ws-security-common/src/main/java/org/apache/wss4j/common/ConfigurationConstants.java?rev=1524969&r1=1524968&r2=1524969&view=diff
==============================================================================
--- webservices/wss4j/trunk/ws-security-common/src/main/java/org/apache/wss4j/common/ConfigurationConstants.java (original)
+++ webservices/wss4j/trunk/ws-security-common/src/main/java/org/apache/wss4j/common/ConfigurationConstants.java Fri Sep 20 10:41:37 2013
@@ -512,6 +512,12 @@ public final class ConfigurationConstant
      */
     public static final String ENABLE_TIMESTAMP_CACHE = "ws-security.enable.timestamp.cache";
     
+    /**
+     * Whether to cache SAML2 Token Identifiers, if the token contains a "OneTimeUse" Condition.
+     * The default value is "true".
+     */
+    public static final String ENABLE_SAML_ONE_TIME_USE_CACHE = "enableSamlOneTimeUseCache";
+    
     //
     // (Non-boolean) Configuration parameters for the actions/processors
     //
@@ -825,6 +831,13 @@ public final class ConfigurationConstant
     public static final String TIMESTAMP_CACHE_INSTANCE = "timestampCacheInstance";
     
     /**
+     * This holds a reference to a ReplayCache instance used to cache SAML2 Token Identifier 
+     * Strings (if the token contains a OneTimeUse Condition). The default instance that is 
+     * used is the EHCacheReplayCache.
+     */
+    public static final String SAML_ONE_TIME_USE_CACHE_INSTANCE = "samlOneTimeUseCacheInstance";
+    
+    /**
      * This holds a reference to a PasswordEncryptor instance, which is used to encrypt or 
      * decrypt passwords in the Merlin Crypto implementation (or any custom Crypto implementations).
      * 

Modified: webservices/wss4j/trunk/ws-security-common/src/main/resources/messages/wss4j_errors.properties
URL: http://svn.apache.org/viewvc/webservices/wss4j/trunk/ws-security-common/src/main/resources/messages/wss4j_errors.properties?rev=1524969&r1=1524968&r2=1524969&view=diff
==============================================================================
--- webservices/wss4j/trunk/ws-security-common/src/main/resources/messages/wss4j_errors.properties (original)
+++ webservices/wss4j/trunk/ws-security-common/src/main/resources/messages/wss4j_errors.properties Fri Sep 20 10:41:37 2013
@@ -14,6 +14,7 @@ badElement = Bad element, expected \"{0}
 badEncAlgo = xenc:EncryptionMethod/@Algorithm is not supported: {0}
 badReferenceURI = Reference URI is null
 badTokenType01 = Bad UsernameToken Values
+badSamlToken = An error happened processing a SAML Token: \"{0}\"
 badUsernameToken = An error happened processing a Username Token: \"{0}\"
 certpath = Error during certificate path validation: {0}
 dataRef = DataReference - referenced data not found

Modified: webservices/wss4j/trunk/ws-security-dom/src/main/java/org/apache/wss4j/dom/handler/RequestData.java
URL: http://svn.apache.org/viewvc/webservices/wss4j/trunk/ws-security-dom/src/main/java/org/apache/wss4j/dom/handler/RequestData.java?rev=1524969&r1=1524968&r2=1524969&view=diff
==============================================================================
--- webservices/wss4j/trunk/ws-security-dom/src/main/java/org/apache/wss4j/dom/handler/RequestData.java (original)
+++ webservices/wss4j/trunk/ws-security-dom/src/main/java/org/apache/wss4j/dom/handler/RequestData.java Fri Sep 20 10:41:37 2013
@@ -91,6 +91,7 @@ public class RequestData {
     protected boolean requireSignedEncryptedDataElements;
     private ReplayCache timestampReplayCache;
     private ReplayCache nonceReplayCache;
+    private ReplayCache samlOneTimeUseReplayCache;
     private Collection<Pattern> subjectDNPatterns = new ArrayList<Pattern>();
     private final List<BSPRule> ignoredBSPRules = new LinkedList<BSPRule>();
     private boolean appendSignatureAfterTimestamp;
@@ -105,6 +106,7 @@ public class RequestData {
     private boolean includeSignatureToken;
     private boolean enableTimestampReplayCache = true;
     private boolean enableNonceReplayCache = true;
+    private boolean enableSamlOneTimeUseReplayCache = true;
     private PasswordEncryptor passwordEncryptor;
 
     public void clear() {
@@ -128,6 +130,7 @@ public class RequestData {
         enableRevocation = false;
         timestampReplayCache = null;
         nonceReplayCache = null;
+        samlOneTimeUseReplayCache = null;
         subjectDNPatterns.clear();
         ignoredBSPRules.clear();
         appendSignatureAfterTimestamp = false;
@@ -142,6 +145,7 @@ public class RequestData {
         includeSignatureToken = false;
         enableTimestampReplayCache = true;
         enableNonceReplayCache = true;
+        setEnableSamlOneTimeUseReplayCache(true);
         passwordEncryptor = null;
     }
 
@@ -555,6 +559,25 @@ public class RequestData {
     }
     
     /**
+     * Set the replay cache for SAML2 OneTimeUse Assertions
+     */
+    public void setSamlOneTimeUseReplayCache(ReplayCache newCache) {
+        samlOneTimeUseReplayCache = newCache;
+    }
+
+    /**
+     * Get the replay cache for SAML2 OneTimeUse Assertions
+     * @throws WSSecurityException 
+     */
+    public ReplayCache getSamlOneTimeUseReplayCache() throws WSSecurityException {
+        if (enableSamlOneTimeUseReplayCache && samlOneTimeUseReplayCache == null) {
+            samlOneTimeUseReplayCache = createCache("wss4j-saml-one-time-use-cache-");
+        }
+        
+        return samlOneTimeUseReplayCache;
+    }
+    
+    /**
      * Set the Signature Subject Cert Constraints
      */
     public void setSubjectCertConstraints(Collection<Pattern> subjectCertConstraints) {
@@ -673,5 +696,13 @@ public class RequestData {
     public void setPasswordEncryptor(PasswordEncryptor passwordEncryptor) {
         this.passwordEncryptor = passwordEncryptor;
     }
+
+    public boolean isEnableSamlOneTimeUseReplayCache() {
+        return enableSamlOneTimeUseReplayCache;
+    }
+
+    public void setEnableSamlOneTimeUseReplayCache(boolean enableSamlOneTimeUseReplayCache) {
+        this.enableSamlOneTimeUseReplayCache = enableSamlOneTimeUseReplayCache;
+    }
         
 }

Modified: webservices/wss4j/trunk/ws-security-dom/src/main/java/org/apache/wss4j/dom/processor/SignatureProcessor.java
URL: http://svn.apache.org/viewvc/webservices/wss4j/trunk/ws-security-dom/src/main/java/org/apache/wss4j/dom/processor/SignatureProcessor.java?rev=1524969&r1=1524968&r2=1524969&view=diff
==============================================================================
--- webservices/wss4j/trunk/ws-security-dom/src/main/java/org/apache/wss4j/dom/processor/SignatureProcessor.java (original)
+++ webservices/wss4j/trunk/ws-security-dom/src/main/java/org/apache/wss4j/dom/processor/SignatureProcessor.java Fri Sep 20 10:41:37 2013
@@ -682,7 +682,7 @@ public class SignatureProcessor implemen
             Date rightNow = new Date();
             long currentTime = rightNow.getTime();
             long expiresTime = expires.getTime();
-            replayCache.add(identifier, (expiresTime - currentTime) / 1000L);
+            replayCache.add(identifier, 1L + (expiresTime - currentTime) / 1000L);
         } else {
             replayCache.add(identifier);
         }

Modified: webservices/wss4j/trunk/ws-security-dom/src/main/java/org/apache/wss4j/dom/validate/SamlAssertionValidator.java
URL: http://svn.apache.org/viewvc/webservices/wss4j/trunk/ws-security-dom/src/main/java/org/apache/wss4j/dom/validate/SamlAssertionValidator.java?rev=1524969&r1=1524968&r2=1524969&view=diff
==============================================================================
--- webservices/wss4j/trunk/ws-security-dom/src/main/java/org/apache/wss4j/dom/validate/SamlAssertionValidator.java (original)
+++ webservices/wss4j/trunk/ws-security-dom/src/main/java/org/apache/wss4j/dom/validate/SamlAssertionValidator.java Fri Sep 20 10:41:37 2013
@@ -19,13 +19,17 @@
 
 package org.apache.wss4j.dom.validate;
 
+import java.util.Date;
 import java.util.List;
 
+import org.apache.wss4j.common.cache.ReplayCache;
 import org.apache.wss4j.common.ext.WSSecurityException;
 import org.apache.wss4j.common.saml.OpenSAMLUtil;
 import org.apache.wss4j.common.saml.SAMLKeyInfo;
 import org.apache.wss4j.common.saml.SamlAssertionWrapper;
 import org.apache.wss4j.dom.handler.RequestData;
+import org.joda.time.DateTime;
+import org.opensaml.common.SAMLVersion;
 
 /**
  * This class validates a SAML Assertion, which is wrapped in an "SamlAssertionWrapper" instance.
@@ -94,6 +98,9 @@ public class SamlAssertionValidator exte
         // Check conditions
         checkConditions(samlAssertion);
         
+        // Check OneTimeUse Condition
+        checkOneTimeUse(samlAssertion, data);
+        
         // Validate the assertion against schemas/profiles
         validateAssertion(samlAssertion);
 
@@ -131,6 +138,41 @@ public class SamlAssertionValidator exte
     }
     
     /**
+     * Check the "OneTimeUse" Condition of the Assertion. If this is set then the Assertion
+     * is cached (if a cache is defined), and must not have been previously cached
+     */
+    protected void checkOneTimeUse(
+        SamlAssertionWrapper samlAssertion, RequestData data
+    ) throws WSSecurityException {
+        if (data.getSamlOneTimeUseReplayCache() != null
+            && samlAssertion.getSamlVersion().equals(SAMLVersion.VERSION_20)
+            && samlAssertion.getSaml2().getConditions() != null
+            && samlAssertion.getSaml2().getConditions().getOneTimeUse() != null) {
+            String identifier = samlAssertion.getId();
+            
+            ReplayCache replayCache = data.getSamlOneTimeUseReplayCache();
+            if (replayCache.contains(identifier)) {
+                throw new WSSecurityException(
+                    WSSecurityException.ErrorCode.INVALID_SECURITY,
+                    "badSamlToken",
+                    "A replay attack has been detected");
+            }
+            
+            DateTime expires = samlAssertion.getSaml2().getConditions().getNotOnOrAfter();
+            if (expires != null) {
+                Date rightNow = new Date();
+                long currentTime = rightNow.getTime();
+                long expiresTime = expires.getMillis();
+                replayCache.add(identifier, 1L + (expiresTime - currentTime) / 1000L);
+            } else {
+                replayCache.add(identifier);
+            }
+            
+            replayCache.add(identifier);
+        }
+    }
+    
+    /**
      * Validate the samlAssertion against schemas/profiles
      */
     protected void validateAssertion(SamlAssertionWrapper samlAssertion) throws WSSecurityException {

Modified: webservices/wss4j/trunk/ws-security-dom/src/test/java/org/apache/wss4j/dom/message/ReplayTest.java
URL: http://svn.apache.org/viewvc/webservices/wss4j/trunk/ws-security-dom/src/test/java/org/apache/wss4j/dom/message/ReplayTest.java?rev=1524969&r1=1524968&r2=1524969&view=diff
==============================================================================
--- webservices/wss4j/trunk/ws-security-dom/src/test/java/org/apache/wss4j/dom/message/ReplayTest.java (original)
+++ webservices/wss4j/trunk/ws-security-dom/src/test/java/org/apache/wss4j/dom/message/ReplayTest.java Fri Sep 20 10:41:37 2013
@@ -30,6 +30,7 @@ import org.apache.wss4j.dom.WSSConfig;
 import org.apache.wss4j.dom.WSSecurityEngine;
 import org.apache.wss4j.dom.WSSecurityEngineResult;
 import org.apache.wss4j.dom.common.KeystoreCallbackHandler;
+import org.apache.wss4j.dom.common.SAML2CallbackHandler;
 import org.apache.wss4j.dom.common.SOAPUtil;
 import org.apache.wss4j.dom.common.SecurityTestUtil;
 import org.apache.wss4j.dom.common.UsernamePasswordCallbackHandler;
@@ -37,6 +38,11 @@ import org.apache.wss4j.common.cache.Mem
 import org.apache.wss4j.common.crypto.Crypto;
 import org.apache.wss4j.common.crypto.CryptoFactory;
 import org.apache.wss4j.common.ext.WSSecurityException;
+import org.apache.wss4j.common.saml.SAMLCallback;
+import org.apache.wss4j.common.saml.SAMLUtil;
+import org.apache.wss4j.common.saml.SamlAssertionWrapper;
+import org.apache.wss4j.common.saml.bean.ConditionsBean;
+import org.apache.wss4j.common.saml.builder.SAML2Constants;
 import org.apache.wss4j.common.util.XMLUtils;
 import org.apache.wss4j.dom.handler.RequestData;
 import org.apache.wss4j.dom.util.WSSecurityUtil;
@@ -435,6 +441,106 @@ public class ReplayTest extends org.juni
     }
     
     /**
+     * Test that creates, sends and processes an unsigned SAML 2 authentication assertion. This
+     * is just a sanity test to make sure that it is possible to send the SAML token twice, as
+     * no "OneTimeUse" Element is defined there is no problem with replaying it.
+     * with a OneTimeUse Element
+     */
+    @org.junit.Test
+    public void testEhCacheReplayedSAML2() throws Exception {
+        SAML2CallbackHandler callbackHandler = new SAML2CallbackHandler();
+        callbackHandler.setStatement(SAML2CallbackHandler.Statement.AUTHN);
+        callbackHandler.setIssuer("www.example.com");
+        callbackHandler.setConfirmationMethod(SAML2Constants.CONF_BEARER);
+        
+        ConditionsBean conditions = new ConditionsBean();
+        conditions.setTokenPeriodMinutes(5);
+            
+        callbackHandler.setConditions(conditions);
+        
+        SAMLCallback samlCallback = new SAMLCallback();
+        SAMLUtil.doSAMLCallback(callbackHandler, samlCallback);
+        SamlAssertionWrapper samlAssertion = new SamlAssertionWrapper(samlCallback);
+
+        WSSecSAMLToken wsSign = new WSSecSAMLToken();
+
+        Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
+        WSSecHeader secHeader = new WSSecHeader();
+        secHeader.insertSecurityHeader(doc);
+        
+        Document unsignedDoc = wsSign.build(doc, samlAssertion, secHeader);
+
+        if (LOG.isDebugEnabled()) {
+            String outputString = XMLUtils.PrettyDocumentToString(unsignedDoc);
+            LOG.debug(outputString);
+        }
+        
+        WSSConfig wssConfig = WSSConfig.getNewInstance();
+        RequestData data = new RequestData();
+        data.setWssConfig(wssConfig);
+        data.setCallbackHandler(callbackHandler);
+        
+        // Successfully verify SAML Token
+        verify(unsignedDoc, wssConfig, data);
+        
+        // Now try again - this should work fine as well
+        verify(unsignedDoc, wssConfig, data);
+    }
+    
+    /**
+     * Test that creates, sends and processes an unsigned SAML 2 authentication assertion
+     * with a OneTimeUse Element
+     */
+    @org.junit.Test
+    public void testEhCacheReplayedSAML2OneTimeUse() throws Exception {
+        SAML2CallbackHandler callbackHandler = new SAML2CallbackHandler();
+        callbackHandler.setStatement(SAML2CallbackHandler.Statement.AUTHN);
+        callbackHandler.setIssuer("www.example.com");
+        callbackHandler.setConfirmationMethod(SAML2Constants.CONF_BEARER);
+        
+        ConditionsBean conditions = new ConditionsBean();
+        conditions.setTokenPeriodMinutes(5);
+        conditions.setOneTimeUse(true);
+            
+        callbackHandler.setConditions(conditions);
+        
+        SAMLCallback samlCallback = new SAMLCallback();
+        SAMLUtil.doSAMLCallback(callbackHandler, samlCallback);
+        SamlAssertionWrapper samlAssertion = new SamlAssertionWrapper(samlCallback);
+
+        WSSecSAMLToken wsSign = new WSSecSAMLToken();
+
+        Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
+        WSSecHeader secHeader = new WSSecHeader();
+        secHeader.insertSecurityHeader(doc);
+        
+        Document unsignedDoc = wsSign.build(doc, samlAssertion, secHeader);
+
+        String outputString = 
+            XMLUtils.PrettyDocumentToString(unsignedDoc);
+        assertTrue(outputString.contains("OneTimeUse"));
+        if (LOG.isDebugEnabled()) {
+            LOG.debug(outputString);
+        }
+        
+        WSSConfig wssConfig = WSSConfig.getNewInstance();
+        RequestData data = new RequestData();
+        data.setWssConfig(wssConfig);
+        data.setCallbackHandler(callbackHandler);
+        
+        // Successfully verify SAML Token
+        verify(unsignedDoc, wssConfig, data);
+        
+        // Now try again - a replay attack should be detected
+        try {
+            verify(unsignedDoc, wssConfig, data);
+            fail("Expected failure on a replay attack");
+        } catch (WSSecurityException ex) {
+            assertTrue(ex.getErrorCode() == WSSecurityException.ErrorCode.INVALID_SECURITY); 
+        }
+    }
+    
+    /**
      * Verifies the soap envelope
      * 
      * @param env soap envelope

Modified: webservices/wss4j/trunk/ws-security-stax/src/main/java/org/apache/wss4j/stax/ConfigurationConverter.java
URL: http://svn.apache.org/viewvc/webservices/wss4j/trunk/ws-security-stax/src/main/java/org/apache/wss4j/stax/ConfigurationConverter.java?rev=1524969&r1=1524968&r2=1524969&view=diff
==============================================================================
--- webservices/wss4j/trunk/ws-security-stax/src/main/java/org/apache/wss4j/stax/ConfigurationConverter.java (original)
+++ webservices/wss4j/trunk/ws-security-stax/src/main/java/org/apache/wss4j/stax/ConfigurationConverter.java Fri Sep 20 10:41:37 2013
@@ -446,6 +446,10 @@ public final class ConfigurationConverte
             decodeBooleanConfigValue(ConfigurationConstants.ENABLE_NONCE_CACHE, true, config);
         properties.setEnableNonceReplayCache(enableNonceCache);
         
+        boolean enableSamlOneTimeUseCache = 
+            decodeBooleanConfigValue(ConfigurationConstants.ENABLE_SAML_ONE_TIME_USE_CACHE, true, config);
+        properties.setEnableSamlOneTimeUseReplayCache(enableSamlOneTimeUseCache);
+        
         boolean encryptSymmetricEncryptionKey = 
             decodeBooleanConfigValue(ConfigurationConstants.ENC_SYM_ENC_KEY, true, config);
         properties.setEncryptSymmetricEncrytionKey(encryptSymmetricEncryptionKey);
@@ -589,6 +593,12 @@ public final class ConfigurationConverte
         if (timestampCache != null) {
             properties.setTimestampReplayCache(timestampCache);
         }
+        
+        ReplayCache samlOneTimeUseCache = 
+            (ReplayCache)config.get(ConfigurationConstants.SAML_ONE_TIME_USE_CACHE_INSTANCE);
+        if (samlOneTimeUseCache != null) {
+            properties.setSamlOneTimeUseReplayCache(samlOneTimeUseCache);
+        }
     }
     
     private static WSSecurityTokenConstants.KeyIdentifier convertKeyIdentifier(String keyIdentifier) {

Modified: webservices/wss4j/trunk/ws-security-stax/src/main/java/org/apache/wss4j/stax/ext/WSSSecurityProperties.java
URL: http://svn.apache.org/viewvc/webservices/wss4j/trunk/ws-security-stax/src/main/java/org/apache/wss4j/stax/ext/WSSSecurityProperties.java?rev=1524969&r1=1524968&r2=1524969&view=diff
==============================================================================
--- webservices/wss4j/trunk/ws-security-stax/src/main/java/org/apache/wss4j/stax/ext/WSSSecurityProperties.java (original)
+++ webservices/wss4j/trunk/ws-security-stax/src/main/java/org/apache/wss4j/stax/ext/WSSSecurityProperties.java Fri Sep 20 10:41:37 2013
@@ -106,8 +106,10 @@ public class WSSSecurityProperties exten
     private boolean enableRevocation = false;
     private ReplayCache timestampReplayCache;
     private ReplayCache nonceReplayCache;
+    private ReplayCache samlOneTimeUseReplayCache;
     private boolean enableTimestampReplayCache = true;
     private boolean enableNonceReplayCache = true;
+    private boolean enableSamlOneTimeUseReplayCache = true;
     private boolean validateSamlSubjectConfirmation = true;
     private Collection<Pattern> subjectDNPatterns = new ArrayList<Pattern>();
 
@@ -152,8 +154,10 @@ public class WSSSecurityProperties exten
         this.enableRevocation = wssSecurityProperties.enableRevocation;
         this.timestampReplayCache = wssSecurityProperties.timestampReplayCache;
         this.nonceReplayCache = wssSecurityProperties.nonceReplayCache;
+        this.samlOneTimeUseReplayCache = wssSecurityProperties.samlOneTimeUseReplayCache;
         this.enableTimestampReplayCache = wssSecurityProperties.enableTimestampReplayCache;
         this.enableNonceReplayCache = wssSecurityProperties.enableNonceReplayCache;
+        this.enableSamlOneTimeUseReplayCache = wssSecurityProperties.enableSamlOneTimeUseReplayCache;
         this.allowRSA15KeyTransportAlgorithm = wssSecurityProperties.allowRSA15KeyTransportAlgorithm;
         this.derivedKeyIterations = wssSecurityProperties.derivedKeyIterations;
         this.useDerivedKeyForMAC = wssSecurityProperties.useDerivedKeyForMAC;
@@ -765,6 +769,25 @@ public class WSSSecurityProperties exten
         
         return nonceReplayCache;
     }
+    
+    /**
+     * Set the replay cache for SAML2 OneTimeUse Assertions
+     */
+    public void setSamlOneTimeUseReplayCache(ReplayCache newCache) {
+        samlOneTimeUseReplayCache = newCache;
+    }
+
+    /**
+     * Get the replay cache for SAML2 OneTimeUse Assertions
+     * @throws WSSecurityException 
+     */
+    public ReplayCache getSamlOneTimeUseReplayCache() throws WSSecurityException {
+        if (enableSamlOneTimeUseReplayCache && samlOneTimeUseReplayCache == null) {
+            samlOneTimeUseReplayCache = createCache("wss4j-saml-one-time-use-cache-");
+        }
+        
+        return samlOneTimeUseReplayCache;
+    }
 
     public boolean isDisableBSPEnforcement() {
         return disableBSPEnforcement;
@@ -861,6 +884,14 @@ public class WSSSecurityProperties exten
     public void setEnableNonceReplayCache(boolean enableNonceReplayCache) {
         this.enableNonceReplayCache = enableNonceReplayCache;
     }
+    
+    public boolean isEnableSamlOneTimeUseReplayCache() {
+        return enableSamlOneTimeUseReplayCache;
+    }
+
+    public void setEnableSamlOneTimeUseReplayCache(boolean enableSamlOneTimeUseReplayCache) {
+        this.enableSamlOneTimeUseReplayCache = enableSamlOneTimeUseReplayCache;
+    }
 
     public boolean isEncryptSymmetricEncrytionKey() {
         return encryptSymmetricEncrytionKey;

Modified: webservices/wss4j/trunk/ws-security-stax/src/main/java/org/apache/wss4j/stax/impl/processor/input/WSSSignatureReferenceVerifyInputProcessor.java
URL: http://svn.apache.org/viewvc/webservices/wss4j/trunk/ws-security-stax/src/main/java/org/apache/wss4j/stax/impl/processor/input/WSSSignatureReferenceVerifyInputProcessor.java?rev=1524969&r1=1524968&r2=1524969&view=diff
==============================================================================
--- webservices/wss4j/trunk/ws-security-stax/src/main/java/org/apache/wss4j/stax/impl/processor/input/WSSSignatureReferenceVerifyInputProcessor.java (original)
+++ webservices/wss4j/trunk/ws-security-stax/src/main/java/org/apache/wss4j/stax/impl/processor/input/WSSSignatureReferenceVerifyInputProcessor.java Fri Sep 20 10:41:37 2013
@@ -188,7 +188,7 @@ public class WSSSignatureReferenceVerify
                 Date rightNow = new Date();
                 long currentTime = rightNow.getTime();
                 long expiresTime = expiresCal.getTimeInMillis();
-                replayCache.add(cacheKey, (expiresTime - currentTime) / 1000L);
+                replayCache.add(cacheKey, 1L + (expiresTime - currentTime) / 1000L);
             } else {
                 replayCache.add(cacheKey);
             }

Modified: webservices/wss4j/trunk/ws-security-stax/src/main/java/org/apache/wss4j/stax/validate/SamlTokenValidatorImpl.java
URL: http://svn.apache.org/viewvc/webservices/wss4j/trunk/ws-security-stax/src/main/java/org/apache/wss4j/stax/validate/SamlTokenValidatorImpl.java?rev=1524969&r1=1524968&r2=1524969&view=diff
==============================================================================
--- webservices/wss4j/trunk/ws-security-stax/src/main/java/org/apache/wss4j/stax/validate/SamlTokenValidatorImpl.java (original)
+++ webservices/wss4j/trunk/ws-security-stax/src/main/java/org/apache/wss4j/stax/validate/SamlTokenValidatorImpl.java Fri Sep 20 10:41:37 2013
@@ -18,6 +18,9 @@
  */
 package org.apache.wss4j.stax.validate;
 
+import java.util.Date;
+
+import org.apache.wss4j.common.cache.ReplayCache;
 import org.apache.wss4j.common.crypto.Crypto;
 import org.apache.wss4j.common.ext.WSSecurityException;
 import org.apache.wss4j.common.saml.SamlAssertionWrapper;
@@ -25,6 +28,8 @@ import org.apache.wss4j.stax.securityTok
 import org.apache.wss4j.stax.impl.securityToken.SamlSecurityTokenImpl;
 import org.apache.wss4j.stax.securityToken.WSSecurityTokenConstants;
 import org.apache.xml.security.stax.securityToken.InboundSecurityToken;
+import org.joda.time.DateTime;
+import org.opensaml.common.SAMLVersion;
 
 public class SamlTokenValidatorImpl extends SignatureTokenValidatorImpl implements SamlTokenValidator {
     
@@ -70,6 +75,11 @@ public class SamlTokenValidatorImpl exte
                                                  final TokenContext tokenContext) throws WSSecurityException {
         // Check conditions
         checkConditions(samlAssertionWrapper);
+        
+        // Check OneTimeUse Condition
+        checkOneTimeUse(samlAssertionWrapper, 
+                        tokenContext.getWssSecurityProperties().getSamlOneTimeUseReplayCache());
+        
         // Validate the assertion against schemas/profiles
         validateAssertion(samlAssertionWrapper);
 
@@ -100,6 +110,38 @@ public class SamlTokenValidatorImpl exte
     }
     
     /**
+     * Check the "OneTimeUse" Condition of the Assertion. If this is set then the Assertion
+     * is cached (if a cache is defined), and must not have been previously cached
+     */
+    protected void checkOneTimeUse(
+        SamlAssertionWrapper samlAssertion, ReplayCache replayCache
+    ) throws WSSecurityException {
+        if (replayCache != null
+            && samlAssertion.getSamlVersion().equals(SAMLVersion.VERSION_20)
+            && samlAssertion.getSaml2().getConditions() != null
+            && samlAssertion.getSaml2().getConditions().getOneTimeUse() != null) {
+            String identifier = samlAssertion.getId();
+            
+            if (replayCache.contains(identifier)) {
+                throw new WSSecurityException(
+                    WSSecurityException.ErrorCode.INVALID_SECURITY,
+                    "badSamlToken",
+                    "A replay attack has been detected");
+            }
+            
+            DateTime expires = samlAssertion.getSaml2().getConditions().getNotOnOrAfter();
+            if (expires != null) {
+                Date rightNow = new Date();
+                long currentTime = rightNow.getTime();
+                long expiresTime = expires.getMillis();
+                replayCache.add(identifier, 1L + (expiresTime - currentTime) / 1000L);
+            } else {
+                replayCache.add(identifier);
+            }
+        }
+    }
+    
+    /**
      * Validate the samlAssertion against schemas/profiles
      */
     protected void validateAssertion(SamlAssertionWrapper samlAssertion) throws WSSecurityException {

Modified: webservices/wss4j/trunk/ws-security-stax/src/test/java/org/apache/wss4j/stax/test/ReplayTest.java
URL: http://svn.apache.org/viewvc/webservices/wss4j/trunk/ws-security-stax/src/test/java/org/apache/wss4j/stax/test/ReplayTest.java?rev=1524969&r1=1524968&r2=1524969&view=diff
==============================================================================
--- webservices/wss4j/trunk/ws-security-stax/src/test/java/org/apache/wss4j/stax/test/ReplayTest.java (original)
+++ webservices/wss4j/trunk/ws-security-stax/src/test/java/org/apache/wss4j/stax/test/ReplayTest.java Fri Sep 20 10:41:37 2013
@@ -29,12 +29,15 @@ import javax.xml.transform.dom.DOMSource
 import javax.xml.transform.stream.StreamResult;
 
 import org.apache.wss4j.common.cache.ReplayCache;
+import org.apache.wss4j.common.saml.bean.ConditionsBean;
+import org.apache.wss4j.common.saml.builder.SAML2Constants;
 import org.apache.wss4j.dom.WSConstants;
 import org.apache.wss4j.dom.handler.WSHandlerConstants;
 import org.apache.wss4j.stax.WSSec;
 import org.apache.wss4j.stax.ext.InboundWSSec;
 import org.apache.wss4j.stax.ext.WSSConstants;
 import org.apache.wss4j.stax.ext.WSSSecurityProperties;
+import org.apache.wss4j.stax.test.saml.SAML2CallbackHandler;
 import org.apache.wss4j.stax.test.utils.StAX2DOM;
 import org.apache.xml.security.exceptions.XMLSecurityException;
 import org.testng.Assert;
@@ -150,4 +153,113 @@ public class ReplayTest extends Abstract
         }
     }
     
+    /**
+     * Test that creates, sends and processes an unsigned SAML 2 authentication assertion. This
+     * is just a sanity test to make sure that it is possible to send the SAML token twice, as
+     * no "OneTimeUse" Element is defined there is no problem with replaying it.
+     * with a OneTimeUse Element
+     */
+    @org.junit.Test
+    public void testEhCacheReplayedSAML2() throws Exception {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        {
+            SAML2CallbackHandler callbackHandler = new SAML2CallbackHandler();
+            callbackHandler.setStatement(SAML2CallbackHandler.Statement.AUTHN);
+            callbackHandler.setConfirmationMethod(SAML2Constants.CONF_BEARER);
+            callbackHandler.setIssuer("www.example.com");
+
+            ConditionsBean conditions = new ConditionsBean();
+            conditions.setTokenPeriodMinutes(5);
+            callbackHandler.setConditions(conditions);
+
+            InputStream sourceDocument = this.getClass().getClassLoader().getResourceAsStream("testdata/plain-soap-1.1.xml");
+            String action = WSHandlerConstants.SAML_TOKEN_UNSIGNED;
+            Properties properties = new Properties();
+            properties.put(WSHandlerConstants.SAML_CALLBACK_REF, callbackHandler);
+            Document securedDocument = doOutboundSecurityWithWSS4J(sourceDocument, action, properties);
+
+            javax.xml.transform.Transformer transformer = TRANSFORMER_FACTORY.newTransformer();
+            transformer.transform(new DOMSource(securedDocument), new StreamResult(baos));
+        }
+
+        // process SAML Token
+        ReplayCache replayCache = null;
+        {
+            WSSSecurityProperties securityProperties = new WSSSecurityProperties();
+            replayCache = securityProperties.getSamlOneTimeUseReplayCache();
+            InboundWSSec wsSecIn = WSSec.getInboundWSSec(securityProperties);
+            XMLStreamReader xmlStreamReader = wsSecIn.processInMessage(xmlInputFactory.createXMLStreamReader(new ByteArrayInputStream(baos.toByteArray())));
+
+            Document document = StAX2DOM.readDoc(documentBuilderFactory.newDocumentBuilder(), xmlStreamReader);
+            Assert.assertNotNull(document);
+        }
+        
+        // now process SAML Token again
+        {
+            WSSSecurityProperties securityProperties = new WSSSecurityProperties();
+            securityProperties.setSamlOneTimeUseReplayCache(replayCache);
+            InboundWSSec wsSecIn = WSSec.getInboundWSSec(securityProperties);
+            XMLStreamReader xmlStreamReader = wsSecIn.processInMessage(xmlInputFactory.createXMLStreamReader(new ByteArrayInputStream(baos.toByteArray())));
+
+            Document document = StAX2DOM.readDoc(documentBuilderFactory.newDocumentBuilder(), xmlStreamReader);
+            Assert.assertNotNull(document);
+        }
+    }
+    
+    /**
+     * Test that creates, sends and processes an unsigned SAML 2 authentication assertion
+     * with a OneTimeUse Element
+     */
+    @org.junit.Test
+    public void testEhCacheReplayedSAML2OneTimeUse() throws Exception {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        {
+            SAML2CallbackHandler callbackHandler = new SAML2CallbackHandler();
+            callbackHandler.setStatement(SAML2CallbackHandler.Statement.AUTHN);
+            callbackHandler.setConfirmationMethod(SAML2Constants.CONF_BEARER);
+            callbackHandler.setIssuer("www.example.com");
+
+            ConditionsBean conditions = new ConditionsBean();
+            conditions.setTokenPeriodMinutes(5);
+            conditions.setOneTimeUse(true);
+            callbackHandler.setConditions(conditions);
+
+            InputStream sourceDocument = this.getClass().getClassLoader().getResourceAsStream("testdata/plain-soap-1.1.xml");
+            String action = WSHandlerConstants.SAML_TOKEN_UNSIGNED;
+            Properties properties = new Properties();
+            properties.put(WSHandlerConstants.SAML_CALLBACK_REF, callbackHandler);
+            Document securedDocument = doOutboundSecurityWithWSS4J(sourceDocument, action, properties);
+
+            javax.xml.transform.Transformer transformer = TRANSFORMER_FACTORY.newTransformer();
+            transformer.transform(new DOMSource(securedDocument), new StreamResult(baos));
+        }
+
+        // process SAML Token
+        ReplayCache replayCache = null;
+        {
+            WSSSecurityProperties securityProperties = new WSSSecurityProperties();
+            replayCache = securityProperties.getSamlOneTimeUseReplayCache();
+            InboundWSSec wsSecIn = WSSec.getInboundWSSec(securityProperties);
+            XMLStreamReader xmlStreamReader = wsSecIn.processInMessage(xmlInputFactory.createXMLStreamReader(new ByteArrayInputStream(baos.toByteArray())));
+
+            Document document = StAX2DOM.readDoc(documentBuilderFactory.newDocumentBuilder(), xmlStreamReader);
+            Assert.assertNotNull(document);
+        }
+        
+        // now process SAML Token again
+        {
+            WSSSecurityProperties securityProperties = new WSSSecurityProperties();
+            securityProperties.setSamlOneTimeUseReplayCache(replayCache);
+            InboundWSSec wsSecIn = WSSec.getInboundWSSec(securityProperties);
+            XMLStreamReader xmlStreamReader = wsSecIn.processInMessage(xmlInputFactory.createXMLStreamReader(new ByteArrayInputStream(baos.toByteArray())));
+
+            try {
+                StAX2DOM.readDoc(documentBuilderFactory.newDocumentBuilder(), xmlStreamReader);
+                Assert.fail("Exception expected");
+            } catch (XMLStreamException e) {
+                org.junit.Assert.assertTrue(e.getCause() instanceof XMLSecurityException);
+            }
+        }
+    }
+    
 }