This is an automated email from the ASF dual-hosted git repository.

coheigea pushed a commit to branch coheigea/sts-caching
in repository https://gitbox.apache.org/repos/asf/cxf.git

commit 9776b8e6b980bfd53519b31157c6b416cedc2229
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Thu Sep 24 06:06:39 2026 +0100

    Adding a more secure way of caching tokens in the STS
---
 .../ws/security/tokenstore/TokenStoreUtils.java    | 150 +++++++++++++++++++
 .../cxf/ws/security/trust/STSTokenValidator.java   |  31 ++--
 .../security/tokenstore/TokenStoreUtilsTest.java   | 162 +++++++++++++++++++++
 .../java/org/apache/cxf/sts/cache/CacheUtils.java  |  22 ++-
 .../cxf/sts/token/provider/SAMLTokenProvider.java  |   2 +-
 .../cxf/sts/token/renewer/SAMLTokenRenewer.java    |  62 +++++++-
 .../sts/token/validator/SAMLTokenValidator.java    |  15 +-
 .../token/validator/UsernameTokenValidator.java    |  12 +-
 .../CustomUsernameTokenProvider.java               |   5 +-
 9 files changed, 409 insertions(+), 52 deletions(-)

diff --git 
a/rt/ws/security/src/main/java/org/apache/cxf/ws/security/tokenstore/TokenStoreUtils.java
 
b/rt/ws/security/src/main/java/org/apache/cxf/ws/security/tokenstore/TokenStoreUtils.java
index 438e5e79e16..69ecf62e998 100644
--- 
a/rt/ws/security/src/main/java/org/apache/cxf/ws/security/tokenstore/TokenStoreUtils.java
+++ 
b/rt/ws/security/src/main/java/org/apache/cxf/ws/security/tokenstore/TokenStoreUtils.java
@@ -18,9 +18,28 @@
  */
 package org.apache.cxf.ws.security.tokenstore;
 
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.HexFormat;
+
+import org.w3c.dom.Element;
+
+import org.apache.cxf.helpers.DOMUtils;
 import org.apache.cxf.message.Message;
 import org.apache.cxf.service.model.EndpointInfo;
 import org.apache.cxf.ws.security.SecurityConstants;
+import org.apache.wss4j.common.ext.WSSecurityException;
+import org.apache.wss4j.common.saml.SamlAssertionWrapper;
+import org.apache.wss4j.common.token.BinarySecurity;
+import org.apache.wss4j.dom.WSConstants;
+import org.apache.wss4j.dom.message.token.SecurityContextToken;
+import org.apache.wss4j.dom.message.token.UsernameToken;
+import org.apache.xml.security.Init;
+import org.apache.xml.security.c14n.Canonicalizer;
 
 /**
  * Some common functionality
@@ -61,4 +80,135 @@ public final class TokenStoreUtils {
             return tokenStore;
         }
     }
+
+    /**
+     * Get a cache key for a signed SAML Assertion, that can be used to store 
and retrieve the (validated)
+     * Assertion in/from a TokenStore. The key is a SHA-256 digest over the 
canonicalized SignedInfo
+     * (which binds the signed content of the Assertion) and the 
SignatureValue. It returns null if the
+     * Assertion is not signed, or if the signature does not conform to the 
SAML signature profile (in which
+     * case the signature might not cover the Assertion itself, and so the 
Assertion must not be cached).
+     *
+     * This method must be used to look up a received Assertion in a 
TokenStore.
+     */
+    public static String getCacheKey(SamlAssertionWrapper assertion) throws 
WSSecurityException {
+        return getCacheKey(assertion, true);
+    }
+
+    /**
+     * Get a cache key for a signed SAML Assertion - see 
getCacheKey(SamlAssertionWrapper).
+     * @param validateSignatureProfile whether to check that the signature 
conforms to the SAML signature
+     *        profile first, returning null if it doesn't. This must be true 
when looking up a received
+     *        Assertion. It can be false when storing an Assertion that was 
just signed by the STS, as
+     *        its DOM Element might not be attached to a Document, which the 
profile validation requires.
+     */
+    public static String getCacheKey(SamlAssertionWrapper assertion, boolean 
validateSignatureProfile)
+        throws WSSecurityException {
+        byte[] signatureValue = assertion.getSignatureValue();
+        if (signatureValue == null || signatureValue.length == 0) {
+            return null;
+        }
+
+        if (validateSignatureProfile) {
+            try {
+                assertion.validateSignatureAgainstProfile();
+            } catch (WSSecurityException ex) {
+                return null;
+            }
+        }
+
+        Element signedInfo = null;
+        Element assertionElement = assertion.getElement();
+        if (assertionElement == null && assertion.getSamlObject() != null) {
+            assertionElement = assertion.getSamlObject().getDOM();
+        }
+        if (assertionElement != null) {
+            Element signature =
+                DOMUtils.getFirstChildWithName(assertionElement, 
WSConstants.SIG_NS, WSConstants.SIG_LN);
+            if (signature != null) {
+                signedInfo = DOMUtils.getFirstChildWithName(signature, 
WSConstants.SIG_NS, "SignedInfo");
+            }
+        }
+        if (signedInfo == null) {
+            return null;
+        }
+
+        try {
+            if (!Init.isInitialized()) {
+                Init.init();
+            }
+            ByteArrayOutputStream signedInfoBytes = new 
ByteArrayOutputStream();
+            
Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS)
+                .canonicalizeSubtree(signedInfo, signedInfoBytes);
+
+            return computeCacheKey("SAML", signedInfoBytes.toByteArray(), 
signatureValue);
+        } catch (Exception ex) {
+            throw new 
WSSecurityException(WSSecurityException.ErrorCode.FAILURE, ex);
+        }
+    }
+
+    /**
+     * Get a cache key for a UsernameToken, that can be used to store and 
retrieve the (validated)
+     * UsernameToken in/from a TokenStore. The key is a SHA-256 digest over 
the UsernameToken values.
+     */
+    public static String getCacheKey(UsernameToken usernameToken) throws 
WSSecurityException {
+        byte[] salt = usernameToken.getSalt();
+        return computeCacheKey("UsernameToken",
+                               toBytes(usernameToken.getName()),
+                               toBytes(usernameToken.getPassword()),
+                               toBytes(usernameToken.getPasswordType()),
+                               toBytes(usernameToken.getNonce()),
+                               toBytes(usernameToken.getCreated()),
+                               salt,
+                               
toBytes(Integer.toString(usernameToken.getIteration())));
+    }
+
+    /**
+     * Get a cache key for a BinarySecurityToken, that can be used to store 
and retrieve the (validated)
+     * BinarySecurityToken in/from a TokenStore. The key is a SHA-256 digest 
over the token values.
+     */
+    public static String getCacheKey(BinarySecurity binarySecurity) {
+        return computeCacheKey("BinarySecurityToken",
+                               toBytes(binarySecurity.getValueType()),
+                               toBytes(binarySecurity.getEncodingType()),
+                               binarySecurity.getToken());
+    }
+
+    /**
+     * Get a cache key for a SecurityContextToken, that can be used to store 
and retrieve the (validated)
+     * SecurityContextToken in/from a TokenStore. The key is a SHA-256 digest 
over the token identifier.
+     */
+    public static String getCacheKey(SecurityContextToken 
securityContextToken) {
+        String identifier = securityContextToken.getIdentifier();
+        if (identifier == null) {
+            return null;
+        }
+        return computeCacheKey("SecurityContextToken", toBytes(identifier));
+    }
+
+    private static byte[] toBytes(String value) {
+        return value == null ? null : value.getBytes(StandardCharsets.UTF_8);
+    }
+
+    private static String computeCacheKey(String tokenType, byte[]... values) {
+        try {
+            ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+            DataOutputStream out = new DataOutputStream(bytes);
+            out.writeUTF(tokenType);
+            for (byte[] value : values) {
+                // Length-prefix each value so that different combinations 
can't produce the same input
+                if (value == null) {
+                    out.writeInt(-1);
+                } else {
+                    out.writeInt(value.length);
+                    out.write(value);
+                }
+            }
+            out.flush();
+
+            MessageDigest digest = MessageDigest.getInstance("SHA-256");
+            return 
HexFormat.of().formatHex(digest.digest(bytes.toByteArray()));
+        } catch (IOException | NoSuchAlgorithmException ex) {
+            throw new IllegalStateException(ex);
+        }
+    }
 }
diff --git 
a/rt/ws/security/src/main/java/org/apache/cxf/ws/security/trust/STSTokenValidator.java
 
b/rt/ws/security/src/main/java/org/apache/cxf/ws/security/trust/STSTokenValidator.java
index df76fb086ff..9b9bf5f91a9 100644
--- 
a/rt/ws/security/src/main/java/org/apache/cxf/ws/security/trust/STSTokenValidator.java
+++ 
b/rt/ws/security/src/main/java/org/apache/cxf/ws/security/trust/STSTokenValidator.java
@@ -20,7 +20,6 @@
 package org.apache.cxf.ws.security.trust;
 
 import java.io.IOException;
-import java.util.Arrays;
 import java.util.List;
 
 import javax.security.auth.callback.Callback;
@@ -86,23 +85,20 @@ public class STSTokenValidator implements Validator {
         try {
             SecurityToken token = new SecurityToken();
             Element tokenElement = null;
-            int hash = 0;
+            String cacheKey = null;
             if (credential.getSamlAssertion() != null) {
                 SamlAssertionWrapper assertion = credential.getSamlAssertion();
-                byte[] signatureValue = assertion.getSignatureValue();
-                if (signatureValue != null && signatureValue.length > 0) {
-                    hash = Arrays.hashCode(signatureValue);
-                }
-                tokenElement = credential.getSamlAssertion().getElement();
+                cacheKey = TokenStoreUtils.getCacheKey(assertion);
+                tokenElement = assertion.getElement();
             } else if (credential.getUsernametoken() != null) {
                 tokenElement = credential.getUsernametoken().getElement();
-                hash = credential.getUsernametoken().hashCode();
+                cacheKey = 
TokenStoreUtils.getCacheKey(credential.getUsernametoken());
             } else if (credential.getBinarySecurityToken() != null) {
                 tokenElement = 
credential.getBinarySecurityToken().getElement();
-                hash = credential.getBinarySecurityToken().hashCode();
+                cacheKey = 
TokenStoreUtils.getCacheKey(credential.getBinarySecurityToken());
             } else if (credential.getSecurityContextToken() != null) {
                 tokenElement = 
credential.getSecurityContextToken().getElement();
-                hash = credential.getSecurityContextToken().hashCode();
+                cacheKey = 
TokenStoreUtils.getCacheKey(credential.getSecurityContextToken());
             }
             token.setToken(tokenElement);
 
@@ -112,8 +108,8 @@ public class STSTokenValidator implements Validator {
                 if (ts == null) {
                     ts = tokenStore;
                 }
-                if (ts != null && hash != 0) {
-                    SecurityToken transformedToken = getTransformedToken(ts, 
hash);
+                if (ts != null && cacheKey != null) {
+                    SecurityToken transformedToken = getTransformedToken(ts, 
cacheKey);
                     if (transformedToken != null && 
!transformedToken.isExpired()) {
                         SamlAssertionWrapper assertion = new 
SamlAssertionWrapper(transformedToken.getToken());
                         credential.setPrincipal(new 
SAMLTokenPrincipalImpl(assertion));
@@ -122,7 +118,6 @@ public class STSTokenValidator implements Validator {
                     }
                 }
             }
-            token.setTokenHash(hash);
 
             STSClient c = stsClient;
             if (c == null) {
@@ -156,10 +151,10 @@ public class STSTokenValidator implements Validator {
                     SamlAssertionWrapper assertion = new 
SamlAssertionWrapper(returnedToken.getToken());
                     credential.setTransformedToken(assertion);
                     credential.setPrincipal(new 
SAMLTokenPrincipalImpl(assertion));
-                    if (!disableCaching && hash != 0 && ts != null) {
+                    if (!disableCaching && cacheKey != null && ts != null) {
                         ts.add(returnedToken);
                         
token.setTransformedTokenIdentifier(returnedToken.getId());
-                        ts.add(Integer.toString(hash), token);
+                        ts.add(cacheKey, token);
                     }
                 }
                 return credential;
@@ -195,9 +190,9 @@ public class STSTokenValidator implements Validator {
         return false;
     }
 
-    private SecurityToken getTransformedToken(TokenStore ts, int hash) {
-        SecurityToken recoveredToken = ts.getToken(Integer.toString(hash));
-        if (recoveredToken != null && recoveredToken.getTokenHash() == hash) {
+    private SecurityToken getTransformedToken(TokenStore ts, String cacheKey) {
+        SecurityToken recoveredToken = ts.getToken(cacheKey);
+        if (recoveredToken != null) {
             String transformedTokenId = 
recoveredToken.getTransformedTokenIdentifier();
             if (transformedTokenId != null) {
                 return ts.getToken(transformedTokenId);
diff --git 
a/rt/ws/security/src/test/java/org/apache/cxf/ws/security/tokenstore/TokenStoreUtilsTest.java
 
b/rt/ws/security/src/test/java/org/apache/cxf/ws/security/tokenstore/TokenStoreUtilsTest.java
new file mode 100644
index 00000000000..99ad53f8727
--- /dev/null
+++ 
b/rt/ws/security/src/test/java/org/apache/cxf/ws/security/tokenstore/TokenStoreUtilsTest.java
@@ -0,0 +1,162 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.ws.security.tokenstore;
+
+import java.io.StringReader;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import org.apache.cxf.helpers.DOMUtils;
+import org.apache.cxf.staxutils.StaxUtils;
+import org.apache.wss4j.common.WSS4JConstants;
+import org.apache.wss4j.common.crypto.Crypto;
+import org.apache.wss4j.common.crypto.CryptoFactory;
+import org.apache.wss4j.common.saml.SAMLCallback;
+import org.apache.wss4j.common.saml.SamlAssertionWrapper;
+import org.apache.wss4j.common.saml.bean.SubjectBean;
+import org.apache.wss4j.common.saml.bean.Version;
+import org.apache.wss4j.common.saml.builder.SAML2Constants;
+import org.apache.wss4j.dom.message.token.UsernameToken;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class TokenStoreUtilsTest {
+
+    @org.junit.Test
+    public void testUnsignedAssertionHasNoCacheKey() throws Exception {
+        SamlAssertionWrapper assertion = createAssertion("alice", false);
+        assertNull(TokenStoreUtils.getCacheKey(assertion));
+    }
+
+    @org.junit.Test
+    public void testSignedAssertionCacheKey() throws Exception {
+        SamlAssertionWrapper assertion = createAssertion("alice", true);
+        String cacheKey = TokenStoreUtils.getCacheKey(assertion);
+        assertNotNull(cacheKey);
+        // SHA-256 in hex
+        assertTrue(cacheKey.matches("[0-9a-f]{64}"));
+
+        // The key must be the same for the Assertion once it has been 
serialized + re-parsed
+        String serialized = StaxUtils.toString(assertion.getElement());
+        Document doc = StaxUtils.read(new StringReader(serialized));
+        SamlAssertionWrapper parsedAssertion = new 
SamlAssertionWrapper(doc.getDocumentElement());
+        assertEquals(cacheKey, TokenStoreUtils.getCacheKey(parsedAssertion));
+    }
+
+    @org.junit.Test
+    public void testIssuedAssertionCacheKey() throws Exception {
+        // An Assertion that was just signed is not attached to a Document, 
and so the (non-validating) key
+        // is used to store it. It must match the key used to look up the 
Assertion when it is received.
+        SamlAssertionWrapper assertion = createAssertion("alice", true, false);
+        String issuedKey = TokenStoreUtils.getCacheKey(assertion, false);
+        assertNotNull(issuedKey);
+
+        String serialized = StaxUtils.toString(assertion.getElement());
+        Document doc = StaxUtils.read(new StringReader(serialized));
+        SamlAssertionWrapper receivedAssertion = new 
SamlAssertionWrapper(doc.getDocumentElement());
+        assertEquals(issuedKey, 
TokenStoreUtils.getCacheKey(receivedAssertion));
+    }
+
+    @org.junit.Test
+    public void testDifferentAssertionsHaveDifferentCacheKeys() throws 
Exception {
+        String aliceKey = TokenStoreUtils.getCacheKey(createAssertion("alice", 
true));
+        String bobKey = TokenStoreUtils.getCacheKey(createAssertion("bob", 
true));
+        assertNotEquals(aliceKey, bobKey);
+    }
+
+    @org.junit.Test
+    public void testModifiedSignedInfoChangesCacheKey() throws Exception {
+        SamlAssertionWrapper assertion = createAssertion("alice", true);
+        String cacheKey = TokenStoreUtils.getCacheKey(assertion);
+
+        // Change the DigestValue of the SignedInfo, keeping the same 
SignatureValue
+        Element digestValue = (Element)assertion.getElement()
+            .getElementsByTagNameNS(WSS4JConstants.SIG_NS, 
"DigestValue").item(0);
+        
digestValue.setTextContent("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=");
+        SamlAssertionWrapper modifiedAssertion = new 
SamlAssertionWrapper(assertion.getElement());
+
+        assertNotEquals(cacheKey, 
TokenStoreUtils.getCacheKey(modifiedAssertion));
+    }
+
+    @org.junit.Test
+    public void testSignatureNotConformingToProfileHasNoCacheKey() throws 
Exception {
+        SamlAssertionWrapper assertion = createAssertion("alice", true);
+        assertNotNull(TokenStoreUtils.getCacheKey(assertion));
+
+        // The signature Reference no longer points to the Assertion itself
+        Element reference = (Element)assertion.getElement()
+            .getElementsByTagNameNS(WSS4JConstants.SIG_NS, 
"Reference").item(0);
+        reference.setAttributeNS(null, "URI", "#some-other-id");
+        SamlAssertionWrapper modifiedAssertion = new 
SamlAssertionWrapper(assertion.getElement());
+
+        assertNull(TokenStoreUtils.getCacheKey(modifiedAssertion));
+    }
+
+    @org.junit.Test
+    public void testUsernameTokenCacheKey() throws Exception {
+        String key1 = TokenStoreUtils.getCacheKey(createUsernameToken("alice", 
"password"));
+        String key2 = TokenStoreUtils.getCacheKey(createUsernameToken("alice", 
"password"));
+        assertEquals(key1, key2);
+        assertTrue(key1.matches("[0-9a-f]{64}"));
+
+        assertNotEquals(key1, 
TokenStoreUtils.getCacheKey(createUsernameToken("bob", "password")));
+        assertNotEquals(key1, 
TokenStoreUtils.getCacheKey(createUsernameToken("alice", "password2")));
+        // Values must not be able to "shift" between fields
+        assertNotEquals(TokenStoreUtils.getCacheKey(createUsernameToken("ab", 
"c")),
+                        TokenStoreUtils.getCacheKey(createUsernameToken("a", 
"bc")));
+    }
+
+    private static UsernameToken createUsernameToken(String name, String 
password) {
+        Document doc = DOMUtils.createDocument();
+        UsernameToken usernameToken = new UsernameToken(true, doc, 
WSS4JConstants.PASSWORD_TEXT);
+        usernameToken.setName(name);
+        usernameToken.setPassword(password);
+        return usernameToken;
+    }
+
+    private static SamlAssertionWrapper createAssertion(String subjectName, 
boolean signed) throws Exception {
+        return createAssertion(subjectName, signed, true);
+    }
+
+    private static SamlAssertionWrapper createAssertion(String subjectName, 
boolean signed, boolean attach)
+        throws Exception {
+        SAMLCallback callback = new SAMLCallback();
+        callback.setSamlVersion(Version.SAML_20);
+        callback.setIssuer("sts");
+        callback.setSubject(new SubjectBean(subjectName, null, 
SAML2Constants.CONF_BEARER));
+
+        SamlAssertionWrapper assertion = new SamlAssertionWrapper(callback);
+        if (signed) {
+            Crypto crypto = CryptoFactory.getInstance("alice.properties");
+            assertion.signAssertion("alice", "password", crypto, false);
+        }
+        Document doc = DOMUtils.createDocument();
+        Element element = assertion.toDOM(doc);
+        if (attach) {
+            // As for a received token, the signature profile validation 
requires the Element to be attached
+            doc.appendChild(element);
+        }
+        return assertion;
+    }
+}
diff --git 
a/services/sts/sts-core/src/main/java/org/apache/cxf/sts/cache/CacheUtils.java 
b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/cache/CacheUtils.java
index 8f566cab8e7..9553ea84cfa 100644
--- 
a/services/sts/sts-core/src/main/java/org/apache/cxf/sts/cache/CacheUtils.java
+++ 
b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/cache/CacheUtils.java
@@ -21,7 +21,6 @@ package org.apache.cxf.sts.cache;
 
 import java.security.Principal;
 import java.time.Instant;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -31,6 +30,9 @@ import org.apache.cxf.sts.STSConstants;
 import org.apache.cxf.sts.request.Renewing;
 import org.apache.cxf.ws.security.tokenstore.SecurityToken;
 import org.apache.cxf.ws.security.tokenstore.TokenStore;
+import org.apache.cxf.ws.security.tokenstore.TokenStoreUtils;
+import org.apache.wss4j.common.ext.WSSecurityException;
+import org.apache.wss4j.common.saml.SamlAssertionWrapper;
 
 public final class CacheUtils {
 
@@ -74,14 +76,20 @@ public final class CacheUtils {
         return securityToken;
     }
 
+    /**
+     * Store the given SecurityToken in the cache, using a (SHA-256 based) key 
derived from the signature
+     * of the given (signed) SAML Assertion - see 
TokenStoreUtils.getCacheKey(SamlAssertionWrapper).
+     * Nothing is stored if the Assertion is not signed. The signature profile 
is not checked here, as the
+     * Assertion might have just been signed by the STS - it is checked when a 
received token is looked up.
+     */
     public static void storeTokenInCache(
         SecurityToken securityToken,
         TokenStore cache,
-        byte[] signatureValue
-    ) {
-        int hash = Arrays.hashCode(signatureValue);
-        securityToken.setTokenHash(hash);
-        String identifier = Integer.toString(hash);
-        cache.add(identifier, securityToken);
+        SamlAssertionWrapper assertion
+    ) throws WSSecurityException {
+        String identifier = TokenStoreUtils.getCacheKey(assertion, false);
+        if (identifier != null) {
+            cache.add(identifier, securityToken);
+        }
     }
 }
diff --git 
a/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/provider/SAMLTokenProvider.java
 
b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/provider/SAMLTokenProvider.java
index 31b9f676767..f5962fd1061 100644
--- 
a/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/provider/SAMLTokenProvider.java
+++ 
b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/provider/SAMLTokenProvider.java
@@ -131,7 +131,7 @@ public class SAMLTokenProvider extends 
AbstractSAMLTokenProvider implements Toke
                         assertion.getNotOnOrAfter(), 
tokenParameters.getPrincipal(), tokenParameters.getRealm(),
                         tokenParameters.getTokenRequirements().getRenewing());
                 CacheUtils.storeTokenInCache(
-                    securityToken, tokenParameters.getTokenStore(), 
signatureValue);
+                    securityToken, tokenParameters.getTokenStore(), assertion);
             }
 
             TokenProviderResponse response = new TokenProviderResponse();
diff --git 
a/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/renewer/SAMLTokenRenewer.java
 
b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/renewer/SAMLTokenRenewer.java
index d7d0d3cdfa5..cfa4e9f7a27 100644
--- 
a/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/renewer/SAMLTokenRenewer.java
+++ 
b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/renewer/SAMLTokenRenewer.java
@@ -23,7 +23,6 @@ import java.security.Principal;
 import java.security.cert.Certificate;
 import java.time.Instant;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -33,6 +32,7 @@ import java.util.logging.Logger;
 
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
+import org.w3c.dom.Node;
 
 import org.apache.cxf.common.logging.LogUtils;
 import org.apache.cxf.helpers.CastUtils;
@@ -51,10 +51,12 @@ import org.apache.cxf.sts.token.realm.RealmProperties;
 import org.apache.cxf.ws.security.sts.provider.STSException;
 import org.apache.cxf.ws.security.tokenstore.SecurityToken;
 import org.apache.cxf.ws.security.tokenstore.TokenStore;
+import org.apache.cxf.ws.security.tokenstore.TokenStoreUtils;
 import org.apache.wss4j.common.WSS4JConstants;
 import org.apache.wss4j.common.crypto.Crypto;
 import org.apache.wss4j.common.ext.WSSecurityException;
 import org.apache.wss4j.common.saml.SAMLKeyInfo;
+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.SAML1ComponentBuilder;
@@ -73,6 +75,7 @@ import org.opensaml.saml.common.SAMLVersion;
 import org.opensaml.saml.saml1.core.Audience;
 import org.opensaml.saml.saml1.core.AudienceRestrictionCondition;
 import org.opensaml.saml.saml2.core.AudienceRestriction;
+import org.opensaml.xmlsec.signature.Signature;
 
 /**
  * A TokenRenewer implementation that renews a (valid or expired) SAML Token.
@@ -176,11 +179,15 @@ public class SAMLTokenRenewer extends 
AbstractSAMLTokenProvider implements Token
         }
 
         try {
-            SamlAssertionWrapper assertion = new 
SamlAssertionWrapper((Element)tokenToRenew.getToken());
+            Element tokenElement = 
getAttachedElement((Element)tokenToRenew.getToken());
+            SamlAssertionWrapper assertion = new 
SamlAssertionWrapper(tokenElement);
 
-            byte[] oldSignature = assertion.getSignatureValue();
-            int hash = Arrays.hashCode(oldSignature);
-            SecurityToken cachedToken = 
tokenStore.getToken(Integer.toString(hash));
+            // Verify the signature, so that the signed content matches the 
(cached) token that was
+            // previously issued or validated. The Assertion is re-signed 
below with the STS key.
+            verifySignature(assertion, tokenElement, tokenParameters);
+
+            String cacheKey = TokenStoreUtils.getCacheKey(assertion);
+            SecurityToken cachedToken = cacheKey != null ? 
tokenStore.getToken(cacheKey) : null;
             if (cachedToken == null) {
                 LOG.log(Level.FINE, "The token to be renewed must be stored in 
the cache");
                 throw new STSException("Can't renew SAML assertion", 
STSException.REQUEST_FAILED);
@@ -193,7 +200,7 @@ public class SAMLTokenRenewer extends 
AbstractSAMLTokenProvider implements Token
             String oldId = createNewId(renewedAssertion);
             // Remove the previous token (now expired) from the cache
             tokenStore.remove(oldId);
-            tokenStore.remove(Integer.toString(hash));
+            tokenStore.remove(cacheKey);
 
             // Create new Conditions & sign the Assertion
             createNewConditions(renewedAssertion, tokenParameters);
@@ -281,6 +288,47 @@ public class SAMLTokenRenewer extends 
AbstractSAMLTokenProvider implements Token
         return Collections.unmodifiableMap(realmMap);
     }
 
+    /**
+     * The signature (profile) validation requires the Element to be attached 
to a Document. This is the case
+     * for a received token, but not e.g. for a token that was just created by 
the SAMLTokenProvider.
+     */
+    private static Element getAttachedElement(Element element) {
+        Node root = element;
+        while (root.getParentNode() != null) {
+            root = root.getParentNode();
+        }
+        if (root.getNodeType() == Node.DOCUMENT_NODE) {
+            return element;
+        }
+
+        Document doc = DOMUtils.createDocument();
+        return (Element)doc.appendChild(doc.importNode(element, true));
+    }
+
+    private void verifySignature(
+        SamlAssertionWrapper assertion,
+        Element tokenElement,
+        TokenRenewerParameters tokenParameters
+    ) throws WSSecurityException {
+        Signature sig = assertion.getSignature();
+        if (sig == null || sig.getKeyInfo() == null) {
+            LOG.log(Level.WARNING, "The token to be renewed must be signed");
+            throw new STSException("Can't renew SAML assertion", 
STSException.REQUEST_FAILED);
+        }
+
+        Crypto sigCrypto = 
tokenParameters.getStsProperties().getSignatureCrypto();
+        RequestData requestData = new RequestData();
+        requestData.setSigVerCrypto(sigCrypto);
+        requestData.setWssConfig(WSSConfig.getNewInstance());
+        requestData.setWsDocInfo(new 
WSDocInfo(tokenElement.getOwnerDocument()));
+
+        SAMLKeyInfo samlKeyInfo =
+            SAMLUtil.getCredentialFromKeyInfo(
+                sig.getKeyInfo().getDOM(), new 
WSSSAMLKeyInfoProcessor(requestData), sigCrypto
+            );
+        assertion.verifySignature(samlKeyInfo);
+    }
+
     private void validateAssertion(
         SamlAssertionWrapper assertion,
         ReceivedToken tokenToRenew,
@@ -522,7 +570,7 @@ public class SAMLTokenRenewer extends 
AbstractSAMLTokenProvider implements Token
                     assertion.getNotOnOrAfter(), 
tokenParameters.getPrincipal(), tokenParameters.getRealm(),
                     tokenParameters.getTokenRequirements().getRenewing());
             CacheUtils.storeTokenInCache(
-                securityToken, tokenParameters.getTokenStore(), 
signatureValue);
+                securityToken, tokenParameters.getTokenStore(), assertion);
         }
     }
 
diff --git 
a/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/validator/SAMLTokenValidator.java
 
b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/validator/SAMLTokenValidator.java
index ab6d46689e0..e6592eedf99 100644
--- 
a/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/validator/SAMLTokenValidator.java
+++ 
b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/validator/SAMLTokenValidator.java
@@ -21,7 +21,6 @@ package org.apache.cxf.sts.token.validator;
 import java.security.Principal;
 import java.security.cert.X509Certificate;
 import java.time.Instant;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -43,6 +42,7 @@ import org.apache.cxf.sts.token.realm.CertConstraintsParser;
 import org.apache.cxf.sts.token.realm.SAMLRealmCodec;
 import org.apache.cxf.ws.security.tokenstore.SecurityToken;
 import org.apache.cxf.ws.security.tokenstore.TokenStore;
+import org.apache.cxf.ws.security.tokenstore.TokenStoreUtils;
 import org.apache.wss4j.common.WSS4JConstants;
 import org.apache.wss4j.common.crypto.Crypto;
 import org.apache.wss4j.common.ext.WSSecurityException;
@@ -179,13 +179,10 @@ public class SAMLTokenValidator implements TokenValidator 
{
             assertion.verifySignature(samlKeyInfo);
 
             SecurityToken secToken = null;
-            byte[] signatureValue = assertion.getSignatureValue();
-            if (tokenParameters.getTokenStore() != null && signatureValue != 
null
-                && signatureValue.length > 0) {
-                int hash = Arrays.hashCode(signatureValue);
-                secToken = 
tokenParameters.getTokenStore().getToken(Integer.toString(hash));
-                if (secToken != null && secToken.getTokenHash() != hash) {
-                    secToken = null;
+            if (tokenParameters.getTokenStore() != null) {
+                String cacheKey = TokenStoreUtils.getCacheKey(assertion);
+                if (cacheKey != null) {
+                    secToken = 
tokenParameters.getTokenStore().getToken(cacheKey);
                 }
             }
             if (secToken != null && secToken.isExpired()) {
@@ -333,7 +330,7 @@ public class SAMLTokenValidator implements TokenValidator {
             SecurityToken securityToken =
                 
CacheUtils.createSecurityTokenForStorage(assertion.getElement(), 
assertion.getId(),
                                                          
assertion.getNotOnOrAfter(), principal, tokenRealm, null);
-            CacheUtils.storeTokenInCache(securityToken, tokenStore, 
signatureValue);
+            CacheUtils.storeTokenInCache(securityToken, tokenStore, assertion);
         }
     }
 
diff --git 
a/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/validator/UsernameTokenValidator.java
 
b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/validator/UsernameTokenValidator.java
index 6555d925f81..6ec17796c40 100644
--- 
a/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/validator/UsernameTokenValidator.java
+++ 
b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/validator/UsernameTokenValidator.java
@@ -48,6 +48,7 @@ import org.apache.cxf.sts.token.realm.UsernameTokenRealmCodec;
 import org.apache.cxf.ws.security.sts.provider.model.ObjectFactory;
 import org.apache.cxf.ws.security.sts.provider.model.secext.UsernameTokenType;
 import org.apache.cxf.ws.security.tokenstore.SecurityToken;
+import org.apache.cxf.ws.security.tokenstore.TokenStoreUtils;
 import org.apache.wss4j.common.WSS4JConstants;
 import org.apache.wss4j.common.bsp.BSPEnforcer;
 import org.apache.wss4j.common.crypto.Crypto;
@@ -176,11 +177,11 @@ public class UsernameTokenValidator implements 
TokenValidator {
             }
 
             // See if the UsernameToken is stored in the cache
-            int hash = ut.hashCode();
+            String cacheKey = TokenStoreUtils.getCacheKey(ut);
             SecurityToken secToken = null;
             if (tokenParameters.getTokenStore() != null) {
-                secToken = 
tokenParameters.getTokenStore().getToken(Integer.toString(hash));
-                if (secToken != null && (secToken.getTokenHash() != hash || 
secToken.isExpired())) {
+                secToken = tokenParameters.getTokenStore().getToken(cacheKey);
+                if (secToken != null && secToken.isExpired()) {
                     secToken = null;
                 }
             }
@@ -226,10 +227,7 @@ public class UsernameTokenValidator implements 
TokenValidator {
             if (tokenParameters.getTokenStore() != null && secToken == null) {
                 secToken = new SecurityToken(ut.getID());
                 secToken.setToken(ut.getElement());
-                int hashCode = ut.hashCode();
-                String identifier = Integer.toString(hashCode);
-                secToken.setTokenHash(hashCode);
-                tokenParameters.getTokenStore().add(identifier, secToken);
+                tokenParameters.getTokenStore().add(cacheKey, secToken);
             }
 
             response.setPrincipal(principal);
diff --git 
a/services/sts/systests/advanced/src/test/java/org/apache/cxf/systest/sts/distributed_caching/CustomUsernameTokenProvider.java
 
b/services/sts/systests/advanced/src/test/java/org/apache/cxf/systest/sts/distributed_caching/CustomUsernameTokenProvider.java
index b9a555a5674..4f28be4e6d1 100644
--- 
a/services/sts/systests/advanced/src/test/java/org/apache/cxf/systest/sts/distributed_caching/CustomUsernameTokenProvider.java
+++ 
b/services/sts/systests/advanced/src/test/java/org/apache/cxf/systest/sts/distributed_caching/CustomUsernameTokenProvider.java
@@ -27,6 +27,7 @@ import 
org.apache.cxf.sts.token.provider.TokenProviderParameters;
 import org.apache.cxf.sts.token.provider.TokenProviderResponse;
 import org.apache.cxf.ws.security.sts.provider.STSException;
 import org.apache.cxf.ws.security.tokenstore.SecurityToken;
+import org.apache.cxf.ws.security.tokenstore.TokenStoreUtils;
 import org.apache.wss4j.common.WSS4JConstants;
 import org.apache.wss4j.dom.message.token.UsernameToken;
 
@@ -67,9 +68,7 @@ public class CustomUsernameTokenProvider implements 
TokenProvider {
             if (tokenParameters.getTokenStore() != null) {
                 SecurityToken securityToken = new 
SecurityToken(usernameToken.getID());
                 securityToken.setToken(usernameToken.getElement());
-                int hashCode = usernameToken.hashCode();
-                String identifier = Integer.toString(hashCode);
-                securityToken.setTokenHash(hashCode);
+                String identifier = TokenStoreUtils.getCacheKey(usernameToken);
                 tokenParameters.getTokenStore().add(identifier, securityToken);
             }
 

Reply via email to