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

more pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git


The following commit(s) were added to refs/heads/master by this push:
     new 7980cda85 KNOX-3040 - Support multiple ways to verify JWT tokens (#915)
7980cda85 is described below

commit 7980cda85d9db922490016e995577c2f5774654b
Author: Sandeep MorĂ© <[email protected]>
AuthorDate: Fri Jun 7 19:47:41 2024 -0400

    KNOX-3040 - Support multiple ways to verify JWT tokens (#915)
    
    * KNOX-3040 - Support multiple ways to verify JWT tokens
    
    * KNOX-3040 - 1) update default algorithm for JWKS 2) Fix unittests
---
 .../provider/federation/jwt/JWTMessages.java       |  8 +++
 .../federation/jwt/filter/AbstractJWTFilter.java   | 15 ++++-
 .../provider/federation/AbstractJWTFilterTest.java | 68 +++++++++++++++++++++-
 .../knox/gateway/hbase/HBaseDispatchTest.java      |  4 +-
 4 files changed, 89 insertions(+), 6 deletions(-)

diff --git 
a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/JWTMessages.java
 
b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/JWTMessages.java
index fb72d9bcb..38604aa6e 100644
--- 
a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/JWTMessages.java
+++ 
b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/JWTMessages.java
@@ -115,4 +115,12 @@ public interface JWTMessages {
   @Message(level = MessageLevel.ERROR, text = "Error while fetching grant type 
and client secret from the request: {0}")
   void errorFetchingClientSecret(String errorMessage, @StackTrace(level = 
MessageLevel.DEBUG) Exception e);
 
+  @Message( level = MessageLevel.INFO, text = "Token verification using 
provided PEM, verified: {0}" )
+  void publicKeyVerification(boolean verified);
+
+  @Message( level = MessageLevel.INFO, text = "Token verification using 
provided JWKS Url, verified: {0}" )
+  void jwksVerification(boolean verified);
+
+  @Message( level = MessageLevel.INFO, text = "Token verification using knox 
signing cert, verified: {0}" )
+  void signingKeyVerification(boolean verified);
 }
diff --git 
a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java
 
b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java
index e372e0d42..81d6ae5e4 100644
--- 
a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java
+++ 
b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java
@@ -45,6 +45,7 @@ import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.knox.gateway.audit.api.Action;
 import org.apache.knox.gateway.audit.api.ActionOutcome;
 import org.apache.knox.gateway.audit.api.AuditContext;
@@ -165,6 +166,9 @@ public abstract class AbstractJWTFilter implements Filter {
     }
 
     expectedSigAlg = filterConfig.getInitParameter(JWT_EXPECTED_SIGALG);
+    if(StringUtils.isBlank(expectedSigAlg)) {
+      expectedSigAlg = JWT_DEFAULT_SIGALG;
+    }
   }
 
   protected List<String> parseExpectedAudiences(String expectedAudiences) {
@@ -509,10 +513,17 @@ public abstract class AbstractJWTFilter implements Filter 
{
       try {
         if (publicKey != null) {
           verified = authority.verifyToken(token, publicKey);
-        } else if (expectedJWKSUrl != null) {
+          log.publicKeyVerification(verified);
+        }
+
+        if (!verified && expectedJWKSUrl != null) {
           verified = authority.verifyToken(token, expectedJWKSUrl, 
expectedSigAlg, allowedJwsTypes);
-        } else {
+          log.jwksVerification(verified);
+        }
+
+        if(!verified) {
           verified = authority.verifyToken(token);
+          log.signingKeyVerification(verified);
         }
       } catch (TokenServiceException e) {
         log.unableToVerifyToken(e);
diff --git 
a/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/AbstractJWTFilterTest.java
 
b/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/AbstractJWTFilterTest.java
index ffd99444b..73ec4c35b 100644
--- 
a/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/AbstractJWTFilterTest.java
+++ 
b/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/AbstractJWTFilterTest.java
@@ -551,6 +551,8 @@ public abstract class AbstractJWTFilterTest  {
   @Test
   public void testInvalidVerificationPEM() throws Exception {
     try {
+
+
       Properties props = getProperties();
 
       KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
@@ -565,7 +567,12 @@ public abstract class AbstractJWTFilterTest  {
 
       props.put(getAudienceProperty(), "bar");
       props.put(getVerificationPemProperty(), failingPem);
-      handler.init(new TestFilterConfig(props));
+
+      /* Create a new handler that has different public key then the token 
signing key */
+      final AbstractJWTFilter handler_new = new TestJWTFederationFilter();
+      ((TestJWTFederationFilter) handler_new).setTokenService(new 
TestJWTokenAuthority(KPair.getPublic()));
+
+      handler_new.init(new TestFilterConfig(props));
 
       SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "alice",
                              new Date(new Date().getTime() + 
TimeUnit.MINUTES.toMillis(10)), privateKey);
@@ -582,7 +589,7 @@ public abstract class AbstractJWTFilterTest  {
       EasyMock.replay(request, response);
 
       TestFilterChain chain = new TestFilterChain();
-      handler.doFilter(request, response, chain);
+      handler_new.doFilter(request, response, chain);
       Assert.assertFalse("doFilterCalled should not be true.", 
chain.doFilterCalled);
       Assert.assertNull("No Subject should be returned.", chain.subject);
     } catch (ServletException se) {
@@ -590,6 +597,63 @@ public abstract class AbstractJWTFilterTest  {
     }
   }
 
+  /**
+   * This will test the signature verification chain in the following order
+   * 1. PEM - check if PEM is configured and signature is validated
+   * 2. JWKS - check if endpoint id configured if not skip
+   * 3. Knox signing key - if the above two fail try to validate using knox 
signing cert
+   * @throws Exception
+   */
+  @Test
+  public void testSignatureVerificationChain() throws Exception {
+    try {
+
+
+      Properties props = getProperties();
+
+      KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
+      kpg.initialize(2048);
+
+      KeyPair KPair = kpg.generateKeyPair();
+      String dn = 
buildDistinguishedName(InetAddress.getLocalHost().getHostName());
+      Certificate cert = X509CertificateUtil.generateCertificate(dn, KPair, 
365, "SHA1withRSA");
+      byte[] data = cert.getEncoded();
+      Base64 encoder = new Base64( 76, "\n".getBytes( 
StandardCharsets.US_ASCII ) );
+      String failingPem = new String(encoder.encodeToString( data ).getBytes( 
StandardCharsets.US_ASCII ), StandardCharsets.US_ASCII).trim();
+
+      props.put(getAudienceProperty(), "bar");
+      /* Add a failing PEN */
+      props.put(getVerificationPemProperty(), failingPem);
+
+      /* This handler is setup with a publicKey, corresponding privateKey is 
used to sign tje JWT below */
+      handler.init(new TestFilterConfig(props));
+
+      SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "alice",
+              new Date(new Date().getTime() + TimeUnit.MINUTES.toMillis(10)), 
privateKey);
+
+      HttpServletRequest request = 
EasyMock.createNiceMock(HttpServletRequest.class);
+      setTokenOnRequest(request, jwt);
+
+      EasyMock.expect(request.getRequestURL()).andReturn(new 
StringBuffer(SERVICE_URL)).anyTimes();
+      EasyMock.expect(request.getPathInfo()).andReturn("resource").anyTimes();
+      EasyMock.expect(request.getQueryString()).andReturn(null);
+      HttpServletResponse response = 
EasyMock.createNiceMock(HttpServletResponse.class);
+      
EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(SERVICE_URL);
+      
EasyMock.expect(response.getOutputStream()).andAnswer(DummyServletOutputStream::new).anyTimes();
+      EasyMock.replay(request, response);
+
+      TestFilterChain chain = new TestFilterChain();
+      handler.doFilter(request, response, chain);
+      Assert.assertTrue("doFilterCalled should be true.", 
chain.doFilterCalled);
+
+      Set<PrimaryPrincipal> principals = 
chain.subject.getPrincipals(PrimaryPrincipal.class);
+      Assert.assertFalse("No PrimaryPrincipal", principals.isEmpty());
+      Assert.assertEquals("Not the expected principal", "alice", 
((Principal)principals.toArray()[0]).getName());
+    } catch (ServletException se) {
+      fail("Should NOT have thrown a ServletException.");
+    }
+  }
+
   @Test
   public void testInvalidIssuer() throws Exception {
     try {
diff --git 
a/gateway-service-hbase/src/test/java/org/apache/knox/gateway/hbase/HBaseDispatchTest.java
 
b/gateway-service-hbase/src/test/java/org/apache/knox/gateway/hbase/HBaseDispatchTest.java
index 0345fc9cd..04cd5187e 100644
--- 
a/gateway-service-hbase/src/test/java/org/apache/knox/gateway/hbase/HBaseDispatchTest.java
+++ 
b/gateway-service-hbase/src/test/java/org/apache/knox/gateway/hbase/HBaseDispatchTest.java
@@ -35,7 +35,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
 public class HBaseDispatchTest {
 
   @SuppressWarnings("deprecation")
-  @Test( timeout = TestUtils.SHORT_TIMEOUT )
+  @Test( timeout = TestUtils.LONG_TIMEOUT )
   public void testGetDispatchUrl() throws Exception {
     HttpServletRequest request;
     Dispatch dispatch;
@@ -84,4 +84,4 @@ public class HBaseDispatchTest {
     uri = dispatch.getDispatchUrl( request );
     assertThat( uri.toASCIIString(), is( 
"http://test-host:42/test,path?test%26name=test%3Dvalue"; ) );
   }
-}
\ No newline at end of file
+}

Reply via email to