This is an automated email from the ASF dual-hosted git repository.
coheigea pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cxf.git
The following commit(s) were added to refs/heads/main by this push:
new d78af62421d Adding a more secure way of caching tokens in the STS
(#3497)
d78af62421d is described below
commit d78af62421d2db6624db1e54b4796bf8f547d636
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Thu Sep 24 13:50:10 2026 +0100
Adding a more secure way of caching tokens in the STS (#3497)
---
.../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);
}