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 952b990a897 ignore x5c and x5t key headers in JWTTokenValidator (#3636)
952b990a897 is described below

commit 952b990a897fdf4f60157bb43cd6efd2ee21a831
Author: Naveed Khan <[email protected]>
AuthorDate: Wed Oct 28 15:44:59 2026 +0530

    ignore x5c and x5t key headers in JWTTokenValidator (#3636)
    
    * ignore x5c and x5t key headers in JWTTokenValidator
    
    * make x5c and x5t key headers opt-in via allowX509KeyHeaders in 
JWTTokenValidator
---
 .../sts/token/validator/jwt/JWTTokenValidator.java | 26 ++++++-
 .../sts/token/validator/JWTTokenValidatorTest.java | 84 ++++++++++++++++++++++
 2 files changed, 108 insertions(+), 2 deletions(-)

diff --git 
a/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/validator/jwt/JWTTokenValidator.java
 
b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/validator/jwt/JWTTokenValidator.java
index d699ebdda19..d35c7e84766 100644
--- 
a/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/validator/jwt/JWTTokenValidator.java
+++ 
b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/validator/jwt/JWTTokenValidator.java
@@ -49,7 +49,7 @@ import org.apache.wss4j.common.crypto.Crypto;
 import org.apache.wss4j.common.crypto.Merlin;
 
 /**
- * Validate a SAML Assertion. It is valid if it was issued and signed by this 
STS.
+ * Validate a JWT Token. It is valid if it was issued and signed by this STS.
  */
 public class JWTTokenValidator implements TokenValidator {
 
@@ -58,6 +58,7 @@ public class JWTTokenValidator implements TokenValidator {
     private int ttl;
     private JWTRoleParser roleParser;
     private JWTRealmCodec realmCodec;
+    private boolean allowX509KeyHeaders;
 
     /**
      * Return true if this TokenValidator implementation is capable of 
validating the
@@ -130,8 +131,12 @@ public class JWTTokenValidator implements TokenValidator {
         KeyStore keystore = ((Merlin)signatureCrypto).getKeyStore();
         verificationProperties.put(JoseConstants.RSSEC_KEY_STORE, keystore);
 
+        // Unless allowX509KeyHeaders is enabled, the token headers are not 
passed on, so the token
+        // is verified with the configured signature certificate only. 
Otherwise x5c, x5t and
+        // x5t#S256 could select any certificate in the keystore as the 
verification key.
         JwsSignatureVerifier signatureVerifier =
-            JwsUtils.loadSignatureVerifier(verificationProperties, 
jwt.getJwsHeaders());
+            JwsUtils.loadSignatureVerifier(verificationProperties,
+                                           allowX509KeyHeaders ? 
jwt.getJwsHeaders() : null);
 
         if (!jwtConsumer.verifySignatureWith(signatureVerifier)) {
             return response;
@@ -210,4 +215,21 @@ public class JWTTokenValidator implements TokenValidator {
     public void setRealmCodec(JWTRealmCodec realmCodec) {
         this.realmCodec = realmCodec;
     }
+
+    public boolean isAllowX509KeyHeaders() {
+        return allowX509KeyHeaders;
+    }
+
+    /**
+     * Whether to honor the x5c, x5t and x5t#S256 headers of the received 
token when selecting the
+     * verification certificate. The default is false, meaning the token is 
only verified with the
+     * certificate of the configured signature username.
+     *
+     * Enabling this makes every certificate in the STS signature crypto a 
trusted JWT issuer, and
+     * with x5c, so is anyone holding a certificate issued by a CA in that 
keystore. If it is enabled,
+     * ideally use a dedicated signature crypto that is not also used as a 
trust store.
+     */
+    public void setAllowX509KeyHeaders(boolean allowX509KeyHeaders) {
+        this.allowX509KeyHeaders = allowX509KeyHeaders;
+    }
 }
diff --git 
a/services/sts/sts-core/src/test/java/org/apache/cxf/sts/token/validator/JWTTokenValidatorTest.java
 
b/services/sts/sts-core/src/test/java/org/apache/cxf/sts/token/validator/JWTTokenValidatorTest.java
index 59304d343b8..3312cb139d0 100644
--- 
a/services/sts/sts-core/src/test/java/org/apache/cxf/sts/token/validator/JWTTokenValidatorTest.java
+++ 
b/services/sts/sts-core/src/test/java/org/apache/cxf/sts/token/validator/JWTTokenValidatorTest.java
@@ -20,6 +20,8 @@ package org.apache.cxf.sts.token.validator;
 
 import java.io.IOException;
 import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
 import java.util.Properties;
 import java.util.Set;
 
@@ -30,10 +32,16 @@ import 
javax.security.auth.callback.UnsupportedCallbackException;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 
+import org.apache.cxf.common.util.Base64UrlUtility;
 import org.apache.cxf.helpers.DOMUtils;
 import org.apache.cxf.jaxws.context.WrappedMessageContext;
 import org.apache.cxf.message.MessageImpl;
+import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm;
+import org.apache.cxf.rs.security.jose.jws.JwsHeaders;
+import org.apache.cxf.rs.security.jose.jws.JwsJwtCompactProducer;
+import org.apache.cxf.rs.security.jose.jws.JwsUtils;
 import org.apache.cxf.rs.security.jose.jwt.JwtClaims;
+import org.apache.cxf.rt.security.crypto.MessageDigestUtils;
 import org.apache.cxf.sts.STSConstants;
 import org.apache.cxf.sts.StaticSTSProperties;
 import org.apache.cxf.sts.cache.DefaultInMemoryTokenStore;
@@ -56,9 +64,11 @@ import org.apache.cxf.ws.security.tokenstore.TokenStore;
 import org.apache.cxf.ws.security.tokenstore.TokenStoreException;
 import org.apache.wss4j.common.crypto.Crypto;
 import org.apache.wss4j.common.crypto.CryptoFactory;
+import org.apache.wss4j.common.crypto.CryptoType;
 import org.apache.wss4j.common.ext.WSPasswordCallback;
 import org.apache.wss4j.common.ext.WSSecurityException;
 import org.apache.wss4j.common.principal.CustomTokenPrincipal;
+import org.apache.wss4j.dom.engine.WSSConfig;
 
 import org.junit.BeforeClass;
 
@@ -157,6 +167,69 @@ public class JWTTokenValidatorTest {
         assertTrue(validatorResponse.getToken().getState() == STATE.INVALID);
     }
 
+    @org.junit.Test
+    public void testTokenSignedByTrustedCertificate() throws Exception {
+        // The STS keystore also holds the trusted certificate of 
"myclientkey". A token signed with
+        // the client's own key must not be accepted as issued by the STS, 
even if its x5t header
+        // points at that certificate.
+        TokenValidatorResponse validatorResponse =
+            validateTokenSignedByTrustedCertificate(new JWTTokenValidator());
+        assertTrue(validatorResponse.getToken().getState() == STATE.INVALID);
+    }
+
+    @org.junit.Test
+    public void testTokenSignedByTrustedCertificateWithX509KeyHeadersAllowed() 
throws Exception {
+        // With allowX509KeyHeaders enabled, the x5t header selects the 
trusted client certificate
+        JWTTokenValidator jwtTokenValidator = new JWTTokenValidator();
+        jwtTokenValidator.setAllowX509KeyHeaders(true);
+
+        TokenValidatorResponse validatorResponse =
+            validateTokenSignedByTrustedCertificate(jwtTokenValidator);
+        assertTrue(validatorResponse.getToken().getState() == STATE.VALID);
+        assertEquals("admin", validatorResponse.getPrincipal().getName());
+    }
+
+    private TokenValidatorResponse validateTokenSignedByTrustedCertificate(
+        TokenValidator jwtTokenValidator) throws Exception {
+        WSSConfig.init();
+        Crypto clientCrypto = 
CryptoFactory.getInstance(getClientCryptoProperties());
+        CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ALIAS);
+        cryptoType.setAlias("myclientkey");
+        X509Certificate clientCert = 
clientCrypto.getX509Certificates(cryptoType)[0];
+        PrivateKey clientKey = clientCrypto.getPrivateKey("myclientkey", 
"cspass");
+
+        JwsHeaders headers = new JwsHeaders(SignatureAlgorithm.RS256);
+        headers.setX509Thumbprint(Base64UrlUtility.encode(
+            MessageDigestUtils.createDigest(clientCert.getEncoded(), 
MessageDigestUtils.ALGO_SHA_1)));
+
+        JwtClaims claims = new JwtClaims();
+        claims.setIssuer("STS");
+        claims.setSubject("admin");
+        long now = System.currentTimeMillis() / 1000L;
+        claims.setIssuedAt(now);
+        claims.setExpiryTime(now + 300L);
+
+        String token = new JwsJwtCompactProducer(headers, claims).signWith(
+            JwsUtils.getPrivateKeySignatureProvider(clientKey, 
SignatureAlgorithm.RS256));
+
+        // Validate the token
+        TokenValidatorParameters validatorParameters = 
createValidatorParameters();
+        TokenRequirements tokenRequirements = 
validatorParameters.getTokenRequirements();
+
+        // Create a ValidateTarget consisting of a JWT Token
+        ReceivedToken validateTarget = new 
ReceivedToken(createTokenWrapper(token));
+        tokenRequirements.setValidateTarget(validateTarget);
+        validatorParameters.setToken(validateTarget);
+
+        assertTrue(jwtTokenValidator.canHandleToken(validateTarget));
+
+        TokenValidatorResponse validatorResponse =
+            jwtTokenValidator.validateToken(validatorParameters);
+        assertNotNull(validatorResponse);
+        assertNotNull(validatorResponse.getToken());
+        return validatorResponse;
+    }
+
     @org.junit.Test
     public void testUnsignedToken() throws Exception {
         // Create
@@ -423,6 +496,17 @@ public class JWTTokenValidatorTest {
         return properties;
     }
 
+    private Properties getClientCryptoProperties() {
+        Properties properties = new Properties();
+        properties.put(
+            "org.apache.wss4j.crypto.provider", 
"org.apache.wss4j.common.crypto.Merlin"
+        );
+        properties.put("org.apache.wss4j.crypto.merlin.keystore.password", 
"cspass");
+        properties.put("org.apache.wss4j.crypto.merlin.keystore.file", 
"keys/clientstore.jks");
+
+        return properties;
+    }
+
     public class EveCallbackHandler implements CallbackHandler {
 
         public void handle(Callback[] callbacks) throws IOException,

Reply via email to