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]