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 bb22563ba46 CXF-9216 - Switch default OAuth2 code verifier to Digest 
(#3132)
bb22563ba46 is described below

commit bb22563ba46b33fee4478e5c4815216da4b49347
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Thu May 21 15:26:26 2026 +0100

    CXF-9216 - Switch default OAuth2 code verifier to Digest (#3132)
    
    * Consolidating test code in PublicClientTest
    
    * CXF-9216 - Switch default OAuth2 code verifier to Digest
    
    * Make the default code verifier configurable
---
 .../grants/code/AuthorizationCodeGrantHandler.java |  20 +++-
 .../security/oauth2/grants/PublicClientTest.java   | 126 +++++++++------------
 .../grants/grants-server-public-session-fips.xml   |  36 ++++++
 .../oauth2/grants/grants-server-public-session.xml |  36 ++++++
 .../oauth2/grants/grants-server-public.xml         |  35 ++++++
 5 files changed, 176 insertions(+), 77 deletions(-)

diff --git 
a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/grants/code/AuthorizationCodeGrantHandler.java
 
b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/grants/code/AuthorizationCodeGrantHandler.java
index b4cd8e93cfb..39e6c79cfb5 100644
--- 
a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/grants/code/AuthorizationCodeGrantHandler.java
+++ 
b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/grants/code/AuthorizationCodeGrantHandler.java
@@ -40,6 +40,7 @@ import org.apache.cxf.rs.security.oauth2.utils.OAuthUtils;
 public class AuthorizationCodeGrantHandler extends AbstractGrantHandler {
 
     private List<CodeVerifierTransformer> codeVerifierTransformers = 
Collections.emptyList();
+    private CodeVerifierTransformer defaultCodeVerifierTransformer = new 
DigestCodeVerifier();
     private boolean expectCodeVerifierForPublicClients;
     private boolean requireCodeVerifier;
 
@@ -173,9 +174,13 @@ public class AuthorizationCodeGrantHandler extends 
AbstractGrantHandler {
                     return false;
                 }
             }
-            // Fall back to plain
+
+            // Fall back to configured default transformer
             if (codeVerifierTransformer == null) {
-                codeVerifierTransformer = new PlainCodeVerifier();
+                if (defaultCodeVerifierTransformer == null) {
+                    return false;
+                }
+                codeVerifierTransformer = defaultCodeVerifierTransformer;
             }
             String transformedCodeVerifier = 
codeVerifierTransformer.transformCodeVerifier(clientCodeVerifier);
             return clientCodeChallenge.equals(transformedCodeVerifier);
@@ -189,8 +194,17 @@ public class AuthorizationCodeGrantHandler extends 
AbstractGrantHandler {
     public void setCodeVerifierTransformers(List<CodeVerifierTransformer> 
codeVerifierTransformers) {
         if (codeVerifierTransformers == null) {
             this.codeVerifierTransformers = Collections.emptyList();
+        } else {
+            this.codeVerifierTransformers = new 
ArrayList<>(codeVerifierTransformers);
         }
-        this.codeVerifierTransformers = new 
ArrayList<>(codeVerifierTransformers);
+    }
+
+    /**
+     * Set a default code verifier transformer used when no specific challenge 
method transformer is matched.
+     * @param defaultCodeVerifierTransformer default code verifier transformer
+     */
+    public void setDefaultCodeVerifierTransformer(CodeVerifierTransformer 
defaultCodeVerifierTransformer) {
+        this.defaultCodeVerifierTransformer = defaultCodeVerifierTransformer;
     }
 
     /**
diff --git 
a/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/oauth2/grants/PublicClientTest.java
 
b/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/oauth2/grants/PublicClientTest.java
index a5f758b0c4d..f3ebc98b8ae 100644
--- 
a/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/oauth2/grants/PublicClientTest.java
+++ 
b/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/oauth2/grants/PublicClientTest.java
@@ -63,11 +63,14 @@ public class PublicClientTest extends 
AbstractClientServerTestBase {
     final String port;
 
     private final String tokenServiceAddress;
+    private final String digestOnlyTokenServiceAddress;
 
     public PublicClientTest(String port) {
         this.port = port;
         // services2 doesn't require basic auth
         tokenServiceAddress = "https://localhost:"; + port + "/services2/";
+        // services3 is configured with DigestCodeVerifier only
+        digestOnlyTokenServiceAddress = "https://localhost:"; + port + 
"/services3/";
     }
 
     @BeforeClass
@@ -124,12 +127,22 @@ public class PublicClientTest extends 
AbstractClientServerTestBase {
 
     @org.junit.Test
     public void testPKCEPlainMissingVerifier() throws Exception {
-        testPKCEMissingVerifier(new PlainCodeVerifier());
+        try {
+            testPKCE(new PlainCodeVerifier(), false, false);
+            fail("Failure expected on a missing verifier");
+        } catch (OAuthServiceException ex) {
+            assertFalse(ex.getError().getError().isEmpty());
+        }
     }
 
     @org.junit.Test
     public void testPKCEPlainDifferentVerifier() throws Exception {
-        testPKCEDifferentVerifier(new PlainCodeVerifier());
+        try {
+            testPKCE(new PlainCodeVerifier(), true, true);
+            fail("Failure expected on a different verifier");
+        } catch (OAuthServiceException ex) {
+            assertFalse(ex.getError().getError().isEmpty());
+        }
     }
 
     @org.junit.Test
@@ -139,83 +152,46 @@ public class PublicClientTest extends 
AbstractClientServerTestBase {
 
     @org.junit.Test
     public void testPKCEDigestMissingVerifier() {
-        testPKCEMissingVerifier(new DigestCodeVerifier());
+        try {
+            testPKCE(new DigestCodeVerifier(), false, false);
+            fail("Failure expected on a missing verifier");
+        } catch (OAuthServiceException ex) {
+            assertFalse(ex.getError().getError().isEmpty());
+        }
     }
 
     @org.junit.Test
     public void testPKCEDigestDifferentVerifier() {
-        testPKCEDifferentVerifier(new DigestCodeVerifier());
-    }
-
-    private void testPKCE(CodeVerifierTransformer transformer) {
-        URL busFile = PublicClientTest.class.getResource("publicclient.xml");
-
-        String address = "https://localhost:"; + port + "/services/";
-        WebClient client = WebClient.create(address, 
OAuth2TestUtils.setupProviders(),
-                                            "alice", "security", 
busFile.toString());
-        // Save the Cookie for the second request...
-        WebClient.getConfig(client).getRequestContext().put(
-            org.apache.cxf.message.Message.MAINTAIN_SESSION, Boolean.TRUE);
-
-        // Get Authorization Code
-        AuthorizationCodeParameters parameters = new 
AuthorizationCodeParameters();
-        parameters.setConsumerId("consumer-id");
-        String codeVerifier = 
Base64UrlUtility.encode(CryptoUtils.generateSecureRandomBytes(32));
-        
parameters.setCodeChallenge(transformer.transformCodeVerifier(codeVerifier));
-        parameters.setCodeChallengeMethod(transformer.getChallengeMethod());
-        parameters.setResponseType(OAuthConstants.CODE_RESPONSE_TYPE);
-        parameters.setPath("authorize/");
-
-        String location = OAuth2TestUtils.getLocation(client, parameters);
-        String code = OAuth2TestUtils.getSubstring(location, "code");
-        assertNotNull(code);
-        client.close();
-
-        // Now get the access token
-        client = WebClient.create(tokenServiceAddress, busFile.toString());
-        ClientAccessToken accessToken =
-            OAuth2TestUtils.getAccessTokenWithAuthorizationCode(client, code, 
"consumer-id", null, codeVerifier);
-        assertNotNull(accessToken.getTokenKey());
-        client.close();
+        try {
+            testPKCE(new DigestCodeVerifier(), true, true);
+            fail("Failure expected on a different verifier");
+        } catch (OAuthServiceException ex) {
+            assertFalse(ex.getError().getError().isEmpty());
+        }
     }
 
-    private void testPKCEMissingVerifier(CodeVerifierTransformer transformer) {
-        URL busFile = PublicClientTest.class.getResource("publicclient.xml");
-
-        String address = "https://localhost:"; + port + "/services/";
-        WebClient client = WebClient.create(address, 
OAuth2TestUtils.setupProviders(),
-                                            "alice", "security", 
busFile.toString());
-        // Save the Cookie for the second request...
-        WebClient.getConfig(client).getRequestContext().put(
-            org.apache.cxf.message.Message.MAINTAIN_SESSION, Boolean.TRUE);
-
-        // Get Authorization Code
-        AuthorizationCodeParameters parameters = new 
AuthorizationCodeParameters();
-        parameters.setConsumerId("consumer-id");
-        String codeVerifier = 
Base64UrlUtility.encode(CryptoUtils.generateSecureRandomBytes(32));
-        
parameters.setCodeChallenge(transformer.transformCodeVerifier(codeVerifier));
-        parameters.setCodeChallengeMethod(transformer.getChallengeMethod());
-        parameters.setResponseType(OAuthConstants.CODE_RESPONSE_TYPE);
-        parameters.setPath("authorize/");
-
-        String location = OAuth2TestUtils.getLocation(client, parameters);
-        String code = OAuth2TestUtils.getSubstring(location, "code");
-        assertNotNull(code);
-        client.close();
-
-        // Now get the access token
-        client = WebClient.create(tokenServiceAddress, busFile.toString());
+    @org.junit.Test
+    public void testPKCEDigestOnlyServer() {
         try {
-            OAuth2TestUtils.getAccessTokenWithAuthorizationCode(client, code, 
"consumer-id", null);
-            fail("Failure expected on a missing verifier");
+            testPKCE(new PlainCodeVerifier(), true, false, 
digestOnlyTokenServiceAddress);
+            fail("Failure expected when plain verifier is not supported");
         } catch (OAuthServiceException ex) {
             assertFalse(ex.getError().getError().isEmpty());
-        } finally {
-            client.close();
         }
+
+        testPKCE(new DigestCodeVerifier(), true, false, 
digestOnlyTokenServiceAddress);
     }
 
-    private void testPKCEDifferentVerifier(CodeVerifierTransformer 
transformer) {
+    private void testPKCE(CodeVerifierTransformer transformer) {
+        testPKCE(transformer, true, false, tokenServiceAddress);
+    }
+
+    private void testPKCE(CodeVerifierTransformer transformer, boolean 
sendVerifier, boolean sendFakeVerifier) {
+        testPKCE(transformer, sendVerifier, sendFakeVerifier, 
tokenServiceAddress);
+    }
+
+    private void testPKCE(CodeVerifierTransformer transformer, boolean 
sendVerifier,
+                          boolean sendFakeVerifier, String serviceAddress) {
         URL busFile = PublicClientTest.class.getResource("publicclient.xml");
 
         String address = "https://localhost:"; + port + "/services/";
@@ -240,14 +216,16 @@ public class PublicClientTest extends 
AbstractClientServerTestBase {
         client.close();
 
         // Now get the access token
-        client = WebClient.create(tokenServiceAddress, busFile.toString());
-
-        codeVerifier = 
Base64UrlUtility.encode(CryptoUtils.generateSecureRandomBytes(32));
+        client = WebClient.create(serviceAddress, busFile.toString());
+        if (!sendVerifier) {
+            codeVerifier = null;
+        } else if (sendFakeVerifier) {
+            codeVerifier = 
Base64UrlUtility.encode(CryptoUtils.generateSecureRandomBytes(32));
+        }
         try {
-            OAuth2TestUtils.getAccessTokenWithAuthorizationCode(client, code, 
"consumer-id", null, codeVerifier);
-            fail("Failure expected on a different verifier");
-        } catch (OAuthServiceException ex) {
-            assertFalse(ex.getError().getError().isEmpty());
+            ClientAccessToken accessToken =
+                OAuth2TestUtils.getAccessTokenWithAuthorizationCode(client, 
code, "consumer-id", null, codeVerifier);
+            assertNotNull(accessToken.getTokenKey());
         } finally {
             client.close();
         }
diff --git 
a/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/oauth2/grants/grants-server-public-session-fips.xml
 
b/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/oauth2/grants/grants-server-public-session-fips.xml
index 77d0a3f78e2..ef090a2c950 100644
--- 
a/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/oauth2/grants/grants-server-public-session-fips.xml
+++ 
b/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/oauth2/grants/grants-server-public-session-fips.xml
@@ -154,6 +154,26 @@ under the License.
          </list>
       </property>
    </bean>
+
+    <bean id="digestOnlyCodeGrantHandler" 
class="org.apache.cxf.rs.security.oauth2.grants.code.AuthorizationCodeGrantHandler">
+        <property name="dataProvider" ref="oauthProvider"/>
+        <property name="requireCodeVerifier" value="true"/>
+        <property name="codeVerifierTransformers">
+            <list>
+                <ref bean="digestVerifier"/>
+            </list>
+        </property>
+    </bean>
+
+   <bean id="digestOnlyTokenService" 
class="org.apache.cxf.rs.security.oauth2.services.AccessTokenService">
+      <property name="dataProvider" ref="oauthProvider"/>
+      <property name="canSupportPublicClients" value="true"/>
+      <property name="grantHandlers">
+         <list>
+             <ref bean="digestOnlyCodeGrantHandler"/>
+         </list>
+      </property>
+   </bean>
    
    <jaxrs:server 
        depends-on="tls-config" 
@@ -170,6 +190,22 @@ under the License.
            <entry key="rs.security.decryption.key.password.provider" 
value-ref="keyPasswordProvider"/>
        </jaxrs:properties>
    </jaxrs:server>
+
+   <jaxrs:server
+       depends-on="tls-config"
+       
address="https://localhost:${testutil.ports.jaxrs-oauth2-grants-jcache-public-session}/services3";>
+       <jaxrs:serviceBeans>
+           <ref bean="digestOnlyTokenService"/>
+       </jaxrs:serviceBeans>
+       <jaxrs:properties>
+           <entry key="rs.security.signature.out.properties" 
value="org/apache/cxf/systest/jaxrs/security/alice.rs-fips.properties"/>
+           <entry key="rs.security.encryption.in.properties" 
value="org/apache/cxf/systest/jaxrs/security/bob.rs-fips.properties"/>
+           <entry key="rs.security.encryption.out.properties" 
value="org/apache/cxf/systest/jaxrs/security/bob.rs-fips.properties"/>
+           <entry key="rs.security.signature.in.properties" 
value="org/apache/cxf/systest/jaxrs/security/alice.rs-fips.properties"/>
+           <entry key="rs.security.signature.key.password.provider" 
value-ref="keyPasswordProvider"/>
+           <entry key="rs.security.decryption.key.password.provider" 
value-ref="keyPasswordProvider"/>
+       </jaxrs:properties>
+   </jaxrs:server>
    
 
 </beans>
diff --git 
a/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/oauth2/grants/grants-server-public-session.xml
 
b/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/oauth2/grants/grants-server-public-session.xml
index 7e638e740d2..5f72adc03f4 100644
--- 
a/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/oauth2/grants/grants-server-public-session.xml
+++ 
b/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/oauth2/grants/grants-server-public-session.xml
@@ -154,6 +154,26 @@ under the License.
          </list>
       </property>
    </bean>
+
+    <bean id="digestOnlyCodeGrantHandler" 
class="org.apache.cxf.rs.security.oauth2.grants.code.AuthorizationCodeGrantHandler">
+        <property name="dataProvider" ref="oauthProvider"/>
+        <property name="requireCodeVerifier" value="true"/>
+        <property name="codeVerifierTransformers">
+            <list>
+                <ref bean="digestVerifier"/>
+            </list>
+        </property>
+    </bean>
+
+   <bean id="digestOnlyTokenService" 
class="org.apache.cxf.rs.security.oauth2.services.AccessTokenService">
+      <property name="dataProvider" ref="oauthProvider"/>
+      <property name="canSupportPublicClients" value="true"/>
+      <property name="grantHandlers">
+         <list>
+             <ref bean="digestOnlyCodeGrantHandler"/>
+         </list>
+      </property>
+   </bean>
    
    <jaxrs:server 
        depends-on="tls-config" 
@@ -170,6 +190,22 @@ under the License.
            <entry key="rs.security.decryption.key.password.provider" 
value-ref="keyPasswordProvider"/>
        </jaxrs:properties>
    </jaxrs:server>
+
+   <jaxrs:server
+       depends-on="tls-config"
+       
address="https://localhost:${testutil.ports.jaxrs-oauth2-grants-jcache-public-session}/services3";>
+       <jaxrs:serviceBeans>
+           <ref bean="digestOnlyTokenService"/>
+       </jaxrs:serviceBeans>
+       <jaxrs:properties>
+           <entry key="rs.security.signature.out.properties" 
value="org/apache/cxf/systest/jaxrs/security/alice.rs.properties"/>
+           <entry key="rs.security.encryption.in.properties" 
value="org/apache/cxf/systest/jaxrs/security/bob.rs.properties"/>
+           <entry key="rs.security.encryption.out.properties" 
value="org/apache/cxf/systest/jaxrs/security/bob.rs.properties"/>
+           <entry key="rs.security.signature.in.properties" 
value="org/apache/cxf/systest/jaxrs/security/alice.rs.properties"/>
+           <entry key="rs.security.signature.key.password.provider" 
value-ref="keyPasswordProvider"/>
+           <entry key="rs.security.decryption.key.password.provider" 
value-ref="keyPasswordProvider"/>
+       </jaxrs:properties>
+   </jaxrs:server>
    
 
 </beans>
diff --git 
a/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/oauth2/grants/grants-server-public.xml
 
b/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/oauth2/grants/grants-server-public.xml
index dea11d8a033..11e3779bfeb 100644
--- 
a/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/oauth2/grants/grants-server-public.xml
+++ 
b/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/oauth2/grants/grants-server-public.xml
@@ -148,6 +148,26 @@ under the License.
          </list>
       </property>
    </bean>
+
+    <bean id="digestOnlyCodeGrantHandler" 
class="org.apache.cxf.rs.security.oauth2.grants.code.AuthorizationCodeGrantHandler">
+        <property name="dataProvider" ref="oauthProvider"/>
+        <property name="requireCodeVerifier" value="true"/>
+        <property name="codeVerifierTransformers">
+             <list>
+                  <ref bean="digestVerifier"/>
+             </list>
+        </property>
+    </bean>
+
+    <bean id="digestOnlyTokenService" 
class="org.apache.cxf.rs.security.oauth2.services.AccessTokenService">
+        <property name="dataProvider" ref="oauthProvider"/>
+        <property name="canSupportPublicClients" value="true"/>
+        <property name="grantHandlers">
+            <list>
+                 <ref bean="digestOnlyCodeGrantHandler"/>
+            </list>
+        </property>
+    </bean>
    
    <jaxrs:server 
        depends-on="tls-config" 
@@ -163,6 +183,21 @@ under the License.
            <entry key="rs.security.signature.algorithm" value="RS256" />
        </jaxrs:properties>
    </jaxrs:server>
+
+   <jaxrs:server
+       depends-on="tls-config"
+       
address="https://localhost:${testutil.ports.jaxrs-oauth2-grants-jcache-public}/services3";>
+       <jaxrs:serviceBeans>
+           <ref bean="digestOnlyTokenService"/>
+       </jaxrs:serviceBeans>
+       <jaxrs:properties>
+           <entry key="rs.security.keystore.type" value="jks" />
+           <entry key="rs.security.keystore.alias" value="alice"/>
+           <entry key="rs.security.keystore.password" value="password"/>
+           <entry key="rs.security.keystore.file" value="keys/alice.jks" />
+           <entry key="rs.security.signature.algorithm" value="RS256" />
+       </jaxrs:properties>
+   </jaxrs:server>
    
 
 </beans>

Reply via email to