From c38f4af239f99968e3327dfb4a31ab2f5cedb78b Mon Sep 17 00:00:00 2001
From: Les Hazlewood <121180+lhazlewood@users.noreply.github.com>
Date: Tue, 4 Feb 2020 14:29:16 -0800
Subject: [PATCH] Ensured DeflateCompressionCodec could fallback to <= 0.10.6
implementation if encountering an IOException. This allows compressed JWTs
created before 0.10.7 to still work. Fixes #536 (#556) (#557)
---
CHANGELOG.md | 3 ++
.../compression/DeflateCompressionCodec.java | 42 +++++++++++++++-
.../DeflateCompressionCodecTest.groovy | 49 +++++++++++++++++++
3 files changed, 92 insertions(+), 2 deletions(-)
create mode 100644 impl/src/test/groovy/io/jsonwebtoken/impl/compression/DeflateCompressionCodecTest.groovy
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e1de24fb..7d7a7ae1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -67,6 +67,9 @@ This patch release:
algorithm name instead of the Java Security Standard Algorithm Name of
[`RSASSA-PSS`](https://docs.oracle.com/en/java/javase/11/docs/specs/security/standard-names.html#signature-algorithms).
This release ensures the standard name is used moving forward.
+
+* Fixes a backwards-compatibility [bug](https://github.com/jwtk/jjwt/issues/536) when parsing compressed JWTs
+ created from 0.10.6 or earlier using the `DEFLATE` compression algorithm.
### 0.10.7
diff --git a/impl/src/main/java/io/jsonwebtoken/impl/compression/DeflateCompressionCodec.java b/impl/src/main/java/io/jsonwebtoken/impl/compression/DeflateCompressionCodec.java
index f31e6c0d..978ced22 100644
--- a/impl/src/main/java/io/jsonwebtoken/impl/compression/DeflateCompressionCodec.java
+++ b/impl/src/main/java/io/jsonwebtoken/impl/compression/DeflateCompressionCodec.java
@@ -15,11 +15,16 @@
*/
package io.jsonwebtoken.impl.compression;
+import io.jsonwebtoken.lang.Objects;
+
import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;
+import java.util.zip.InflaterOutputStream;
/**
* Codec implementing the deflate compression algorithm.
@@ -48,7 +53,40 @@ public class DeflateCompressionCodec extends AbstractCompressionCodec {
}
@Override
- protected byte[] doDecompress(byte[] compressed) throws IOException {
- return readAndClose(new InflaterInputStream(new ByteArrayInputStream(compressed)));
+ protected byte[] doDecompress(final byte[] compressed) throws IOException {
+ try {
+ return readAndClose(new InflaterInputStream(new ByteArrayInputStream(compressed)));
+ } catch (IOException e1) {
+ try {
+ return doDecompressBackCompat(compressed);
+ } catch (IOException e2) {
+ throw e1; //retain/report original exception
+ }
+ }
+ }
+
+ /**
+ * This implementation was in 0.10.6 and earlier - it will be used as a fallback for backwards compatibility if
+ * {@link #readAndClose(InputStream)} fails per Issue 536.
+ *
+ * @param compressed the compressed byte array
+ * @return decompressed bytes
+ * @throws IOException if unable to decompress using the 0.10.6 and earlier logic
+ * @since 0.10.8
+ */
+ // package protected on purpose
+ byte[] doDecompressBackCompat(byte[] compressed) throws IOException {
+ InflaterOutputStream inflaterOutputStream = null;
+ ByteArrayOutputStream decompressedOutputStream = null;
+
+ try {
+ decompressedOutputStream = new ByteArrayOutputStream();
+ inflaterOutputStream = new InflaterOutputStream(decompressedOutputStream);
+ inflaterOutputStream.write(compressed);
+ inflaterOutputStream.flush();
+ return decompressedOutputStream.toByteArray();
+ } finally {
+ Objects.nullSafeClose(decompressedOutputStream, inflaterOutputStream);
+ }
}
}
diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/compression/DeflateCompressionCodecTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/compression/DeflateCompressionCodecTest.groovy
new file mode 100644
index 00000000..52f2b4e1
--- /dev/null
+++ b/impl/src/test/groovy/io/jsonwebtoken/impl/compression/DeflateCompressionCodecTest.groovy
@@ -0,0 +1,49 @@
+package io.jsonwebtoken.impl.compression
+
+import io.jsonwebtoken.CompressionException
+import io.jsonwebtoken.Jwts
+import io.jsonwebtoken.io.Decoders
+import org.junit.Test
+
+import static org.junit.Assert.assertNotSame
+
+/**
+ * @since 0.10.8
+ */
+class DeflateCompressionCodecTest {
+
+ /**
+ * Test case for Issue 536.
+ */
+ @Test
+ void testBackwardsCompatibility_0_10_6() {
+ final String jwtFrom0106 = 'eyJhbGciOiJub25lIiwiemlwIjoiREVGIn0.eNqqVsosLlayUspNVdJRKi5NAjJLi1OLgJzMxBIlK0sTMzMLEwsDAx2l1IoCJSsTQwMjExOQQC0AAAD__w.'
+ Jwts.parserBuilder().build().parseClaimsJwt(jwtFrom0106) // no exception should be thrown
+ }
+
+ /**
+ * Test to ensure that, even if the backwards-compatibility fallback method throws an exception, that the first
+ * one is retained/re-thrown to reflect the correct/expected implementation.
+ */
+ @Test
+ void testBackwardsCompatibilityRetainsFirstIOException() {
+
+ final String compressedFrom0_10_6 = 'eNqqVsosLlayUspNVdJRKi5NAjJLi1OLgJzMxBIlK0sTMzMLEwsDAx2l1IoCJSsTQwMjExOQQC0AAAD__w'
+ byte[] invalid = Decoders.BASE64URL.decode(compressedFrom0_10_6)
+
+ IOException unexpected = new IOException("foo")
+
+ def codec = new DeflateCompressionCodec() {
+ @Override
+ byte[] doDecompressBackCompat(byte[] compressed) throws IOException {
+ throw unexpected
+ }
+ }
+
+ try {
+ codec.decompress(invalid)
+ } catch (CompressionException ce) {
+ assertNotSame(unexpected, ce.getCause())
+ }
+ }
+}