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

rcordier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git


The following commit(s) were added to refs/heads/master by this push:
     new e08a7cd30c [FIX] JWT should not attempt to unzip data by default 
(#2189)
e08a7cd30c is described below

commit e08a7cd30cb69600602e42c79841afd90a3c6d93
Author: Benoit TELLIER <[email protected]>
AuthorDate: Wed Apr 10 10:18:32 2024 +0700

    [FIX] JWT should not attempt to unzip data by default (#2189)
---
 pom.xml                                            |  1 -
 .../sample-configuration/jvm.properties            |  5 +++-
 .../sample-configuration/jvm.properties            |  3 ++
 .../jpa-app/sample-configuration/jvm.properties    |  5 +++-
 .../sample-configuration/jvm.properties            |  5 +++-
 .../memory-app/sample-configuration/jvm.properties |  5 +++-
 .../sample-configuration/jvm.properties            |  3 ++
 server/protocols/jwt/pom.xml                       |  1 -
 .../org/apache/james/jwt/JwtTokenVerifier.java     | 21 ++++++++++++++
 .../apache/james/jwt/OidcJwtTokenVerifierTest.java | 32 ++++++++++++++++++++++
 .../org/apache/james/jwt/OidcTokenFixture.java     |  7 +++--
 11 files changed, 79 insertions(+), 9 deletions(-)

diff --git a/pom.xml b/pom.xml
index 5ea2731640..1113e01547 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2409,7 +2409,6 @@
                 <groupId>io.jsonwebtoken</groupId>
                 <artifactId>jjwt-impl</artifactId>
                 <version>${jjwt.version}</version>
-                <scope>runtime</scope>
             </dependency>
             <dependency>
                 <groupId>io.jsonwebtoken</groupId>
diff --git a/server/apps/cassandra-app/sample-configuration/jvm.properties 
b/server/apps/cassandra-app/sample-configuration/jvm.properties
index 9415002bc7..03997ab5f5 100644
--- a/server/apps/cassandra-app/sample-configuration/jvm.properties
+++ b/server/apps/cassandra-app/sample-configuration/jvm.properties
@@ -62,4 +62,7 @@ jmx.remote.x.mlet.allow.getMBeansFromURL=false
 
 # Value from which dedicated BodyFactory shall start buffering data to a file.
 # Used for attachment parsing upon message creation. Default value: 100K.
-# james.mime4j.buffered.body.factory.file.threshold=100K
\ No newline at end of file
+# james.mime4j.buffered.body.factory.file.threshold=100K
+
+# Whether James should unzip JWTs. Default to false
+# james.jwt.zip.allow=false
\ No newline at end of file
diff --git 
a/server/apps/distributed-pop3-app/sample-configuration/jvm.properties 
b/server/apps/distributed-pop3-app/sample-configuration/jvm.properties
index 3676aa5c89..181a7c302b 100644
--- a/server/apps/distributed-pop3-app/sample-configuration/jvm.properties
+++ b/server/apps/distributed-pop3-app/sample-configuration/jvm.properties
@@ -53,3 +53,6 @@ james.jmx.credential.generation=true
 # Disable Remote Code Execution feature from JMX
 # CF 
https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/19fb8f93c59dfd791f62d41f332db9e306bc1422/src/java.management/share/classes/com/sun/jmx/remote/security/MBeanServerAccessController.java#L646
 jmx.remote.x.mlet.allow.getMBeansFromURL=false
+
+# Whether James should unzip JWTs. Default to false
+# james.jwt.zip.allow=false
\ No newline at end of file
diff --git a/server/apps/jpa-app/sample-configuration/jvm.properties 
b/server/apps/jpa-app/sample-configuration/jvm.properties
index 7154210df7..4ebef369c5 100644
--- a/server/apps/jpa-app/sample-configuration/jvm.properties
+++ b/server/apps/jpa-app/sample-configuration/jvm.properties
@@ -50,4 +50,7 @@ james.jmx.credential.generation=true
 # Disable Remote Code Execution feature from JMX
 # CF 
https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/19fb8f93c59dfd791f62d41f332db9e306bc1422/src/java.management/share/classes/com/sun/jmx/remote/security/MBeanServerAccessController.java#L646
 jmx.remote.x.mlet.allow.getMBeansFromURL=false
-openjpa.Multithreaded=true
\ No newline at end of file
+openjpa.Multithreaded=true
+
+# Whether James should unzip JWTs. Default to false
+# james.jwt.zip.allow=false
\ No newline at end of file
diff --git a/server/apps/jpa-smtp-app/sample-configuration/jvm.properties 
b/server/apps/jpa-smtp-app/sample-configuration/jvm.properties
index 7154210df7..4ebef369c5 100644
--- a/server/apps/jpa-smtp-app/sample-configuration/jvm.properties
+++ b/server/apps/jpa-smtp-app/sample-configuration/jvm.properties
@@ -50,4 +50,7 @@ james.jmx.credential.generation=true
 # Disable Remote Code Execution feature from JMX
 # CF 
https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/19fb8f93c59dfd791f62d41f332db9e306bc1422/src/java.management/share/classes/com/sun/jmx/remote/security/MBeanServerAccessController.java#L646
 jmx.remote.x.mlet.allow.getMBeansFromURL=false
-openjpa.Multithreaded=true
\ No newline at end of file
+openjpa.Multithreaded=true
+
+# Whether James should unzip JWTs. Default to false
+# james.jwt.zip.allow=false
\ No newline at end of file
diff --git a/server/apps/memory-app/sample-configuration/jvm.properties 
b/server/apps/memory-app/sample-configuration/jvm.properties
index 8a4c348b36..1834ee9cbc 100644
--- a/server/apps/memory-app/sample-configuration/jvm.properties
+++ b/server/apps/memory-app/sample-configuration/jvm.properties
@@ -52,4 +52,7 @@ james.jmx.credential.generation=true
 jmx.remote.x.mlet.allow.getMBeansFromURL=false
 
 # Default charset to use in JMAP to present text body parts
-# james.jmap.default.charset=US-ASCII
\ No newline at end of file
+# james.jmap.default.charset=US-ASCII
+
+# Whether James should unzip JWTs. Default to false
+# james.jwt.zip.allow=false
\ No newline at end of file
diff --git 
a/server/apps/scaling-pulsar-smtp/sample-configuration/jvm.properties 
b/server/apps/scaling-pulsar-smtp/sample-configuration/jvm.properties
index 4fb3f69a0f..df272a165b 100644
--- a/server/apps/scaling-pulsar-smtp/sample-configuration/jvm.properties
+++ b/server/apps/scaling-pulsar-smtp/sample-configuration/jvm.properties
@@ -43,3 +43,6 @@
 # JMX, when enable causes RMI to plan System.gc every hour. Set this instead 
to once every 1000h.
 #sun.rmi.dgc.server.gcInterval=3600000000
 #sun.rmi.dgc.client.gcInterval=3600000000
+
+# Whether James should unzip JWTs. Default to false
+# james.jwt.zip.allow=false
diff --git a/server/protocols/jwt/pom.xml b/server/protocols/jwt/pom.xml
index 182885887d..157f6f002b 100644
--- a/server/protocols/jwt/pom.xml
+++ b/server/protocols/jwt/pom.xml
@@ -69,7 +69,6 @@
         <dependency>
             <groupId>io.jsonwebtoken</groupId>
             <artifactId>jjwt-impl</artifactId>
-            <scope>runtime</scope>
         </dependency>
         <dependency>
             <groupId>io.jsonwebtoken</groupId>
diff --git 
a/server/protocols/jwt/src/main/java/org/apache/james/jwt/JwtTokenVerifier.java 
b/server/protocols/jwt/src/main/java/org/apache/james/jwt/JwtTokenVerifier.java
index c5bc4ff9b8..878a8a6c76 100644
--- 
a/server/protocols/jwt/src/main/java/org/apache/james/jwt/JwtTokenVerifier.java
+++ 
b/server/protocols/jwt/src/main/java/org/apache/james/jwt/JwtTokenVerifier.java
@@ -25,16 +25,36 @@ import java.util.Optional;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
 
 import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.CompressionCodecResolver;
 import io.jsonwebtoken.Jws;
 import io.jsonwebtoken.JwtException;
 import io.jsonwebtoken.JwtParser;
 import io.jsonwebtoken.Jwts;
 import io.jsonwebtoken.MalformedJwtException;
+import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver;
 
 public class JwtTokenVerifier {
+    private static final CompressionCodecResolver 
DEFAULT_COMPRESSION_CODEC_RESOLVER = new DefaultCompressionCodecResolver();
+    private static final CompressionCodecResolver 
SECURE_COMPRESSION_CODEC_RESOLVER = header -> {
+        if (Optional.ofNullable(header.getCompressionAlgorithm()).isPresent()) 
{
+            throw new RuntimeException("Rejecting a ZIP JWT. Usage of ZIPPED 
JWT can result in " +
+                "excessive memory usage with malicious JWT tokens. To activate 
support for ZIPPed" +
+                "JWT please run James with the -Djames.jwt.zip.allow=true 
system property.");
+        }
+        return 
DEFAULT_COMPRESSION_CODEC_RESOLVER.resolveCompressionCodec(header);
+    };
+    private static final boolean allowZipJWT = 
Optional.ofNullable(System.getProperty("james.jwt.zip.allow"))
+        .map(Boolean::parseBoolean)
+        .orElse(false);
+    @VisibleForTesting
+    static CompressionCodecResolver CONFIGURED_COMPRESSION_CODEC_RESOLVER = 
Optional.of(allowZipJWT)
+        .filter(b -> b)
+        .map(any -> DEFAULT_COMPRESSION_CODEC_RESOLVER)
+        .orElse(SECURE_COMPRESSION_CODEC_RESOLVER);
 
     public interface Factory {
         JwtTokenVerifier create();
@@ -97,6 +117,7 @@ public class JwtTokenVerifier {
     private JwtParser toImmutableJwtParser(PublicKey publicKey) {
         return Jwts.parserBuilder()
             .setSigningKey(publicKey)
+            .setCompressionCodecResolver(CONFIGURED_COMPRESSION_CODEC_RESOLVER)
             .build();
     }
 }
diff --git 
a/server/protocols/jwt/src/test/java/org/apache/james/jwt/OidcJwtTokenVerifierTest.java
 
b/server/protocols/jwt/src/test/java/org/apache/james/jwt/OidcJwtTokenVerifierTest.java
index ff18fcf56a..0d146a865f 100644
--- 
a/server/protocols/jwt/src/test/java/org/apache/james/jwt/OidcJwtTokenVerifierTest.java
+++ 
b/server/protocols/jwt/src/test/java/org/apache/james/jwt/OidcJwtTokenVerifierTest.java
@@ -22,6 +22,7 @@ package org.apache.james.jwt;
 import static org.apache.james.jwt.OidcTokenFixture.INTROSPECTION_RESPONSE;
 import static org.apache.james.jwt.OidcTokenFixture.USERINFO_RESPONSE;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 import java.net.MalformedURLException;
@@ -40,6 +41,10 @@ import org.mockserver.integration.ClientAndServer;
 import org.mockserver.model.HttpRequest;
 import org.mockserver.model.HttpResponse;
 
+import io.jsonwebtoken.CompressionCodecs;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver;
 import reactor.core.publisher.Mono;
 
 class OidcJwtTokenVerifierTest {
@@ -86,6 +91,33 @@ class OidcJwtTokenVerifierTest {
         });
     }
 
+    @Test
+    void shouldRejectZippedJWTByDefault() {
+        String jws = Jwts.builder()
+            .claim("kid", "a".repeat(100))
+            .compressWith(CompressionCodecs.DEFLATE)
+            .signWith(SignatureAlgorithm.HS256, 
OidcTokenFixture.PRIVATE_KEY_BASE64.replace("\n", ""))
+            .compact();
+
+        assertThatThrownBy(() -> 
OidcJwtTokenVerifier.verifySignatureAndExtractClaim(jws, getJwksURL(), "kid"))
+            .isInstanceOf(RuntimeException.class)
+            .hasMessageContaining("Rejecting a ZIP JWT");
+    }
+
+    @Test
+    void shouldAcceptZippedJWTWhenConfigured() {
+        String jws = Jwts.builder()
+            .claim("kid", "a".repeat(100))
+            .compressWith(CompressionCodecs.DEFLATE)
+            .signWith(SignatureAlgorithm.HS256, 
OidcTokenFixture.PRIVATE_KEY_BASE64.replace("\n", ""))
+            .compact();
+
+        JwtTokenVerifier.CONFIGURED_COMPRESSION_CODEC_RESOLVER = new 
DefaultCompressionCodecResolver();
+
+        assertThatCode(() -> 
OidcJwtTokenVerifier.verifySignatureAndExtractClaim(jws, getJwksURL(), "kid"))
+            .doesNotThrowAnyException();
+    }
+
     @Test
     void verifyAndClaimShouldReturnEmptyWhenValidTokenHasNotFoundKid() {
         
assertThat(OidcJwtTokenVerifier.verifySignatureAndExtractClaim(OidcTokenFixture.VALID_TOKEN_HAS_NOT_FOUND_KID,
 getJwksURL(), "email_address"))
diff --git 
a/server/protocols/jwt/src/test/java/org/apache/james/jwt/OidcTokenFixture.java 
b/server/protocols/jwt/src/test/java/org/apache/james/jwt/OidcTokenFixture.java
index eafd93a38f..d07646e5c4 100644
--- 
a/server/protocols/jwt/src/test/java/org/apache/james/jwt/OidcTokenFixture.java
+++ 
b/server/protocols/jwt/src/test/java/org/apache/james/jwt/OidcTokenFixture.java
@@ -21,8 +21,7 @@ package org.apache.james.jwt;
 
 public class OidcTokenFixture {
 
-    public static final String PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----\n" +
-        "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCroSIEhNYajXzC\n" +
+    public static final String PRIVATE_KEY_BASE64 = 
"MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCroSIEhNYajXzC\n" +
         "gsn+xetgjqYc/SaihaHCIjWra2xMbkyl42BITRmjBFGbUxThMEg5YvaBXC1XQeib\n" +
         "auW7gJnBZs8S54K5FMyjgUXOKbjHqPRxE76vUaIYkAZoeufAnXosfDf/XUZTTKE2\n" +
         "yxyZJhdfgU/RSEpN19joXfskIQWmIXlMKkIG9lGqj7eIcyomdlHHuYxb9owqU+lP\n" +
@@ -47,7 +46,9 @@ public class OidcTokenFixture {
         "EdbOTUKhdenKEcSvICOjCrRL/sZHQCSZCH+d7UkCgYAbGniqH/pp73sGd9NZyVT/\n" +
         "HJdOH5dfBfS9sBBJ1f0/pySJLKcArOXS9BMIFueOq4EIc+7hKDCQuqeyhpYZ6UCe\n" +
         "C9h0QNig49qGI/UEtlNrIlydHyPinTa1fDqu99EuRHG0d4RuONW45tmZAY7mGIbf\n" +
-        "PRhJhwOHZT9xO+uPrtQIAw==\n" +
+        "PRhJhwOHZT9xO+uPrtQIAw==\n";
+    public static final String PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----\n" +
+        PRIVATE_KEY_BASE64 +
         "-----END PRIVATE KEY-----";
 
     public static final String PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\n" +


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to