Skip to content

Commit 3489fdb

Browse files
authored
JWE arbitrary content compression (#937)
Closes #938. * Fix compression being omitted for JWEs with arbitrary content The JWE content was compressed only when claims was used as payload, but when using arbitrary content, the compression was omitted, while keeping the "zip" header field, leading to decompression failing. * Refactor duplicate payload -> input stream logic from sign()/encrypt() * Preserve the content name * Fix name for JWE payload
1 parent 23d9a33 commit 3489fdb

File tree

2 files changed

+106
-16
lines changed

2 files changed

+106
-16
lines changed

impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java

+14-16
Original file line numberDiff line numberDiff line change
@@ -595,14 +595,8 @@ private String sign(final Payload payload, final Key key, final Provider provide
595595

596596
// Next, b64 extension requires the raw (non-encoded) payload to be included directly in the signing input,
597597
// so we ensure we have an input stream for that:
598-
if (payload.isClaims() || payload.isCompressed()) {
599-
ByteArrayOutputStream claimsOut = new ByteArrayOutputStream(8192);
600-
writeAndClose("JWS Unencoded Payload", payload, claimsOut);
601-
payloadStream = Streams.of(claimsOut.toByteArray());
602-
} else {
603-
// No claims and not compressed, so just get the direct InputStream:
604-
payloadStream = Assert.stateNotNull(payload.toInputStream(), "Payload InputStream cannot be null.");
605-
}
598+
payloadStream = toInputStream("JWS Unencoded Payload", payload);
599+
606600
if (!payload.isClaims()) {
607601
payloadStream = new CountingInputStream(payloadStream); // we'll need to assert if it's empty later
608602
}
@@ -693,14 +687,7 @@ private String encrypt(final Payload content, final Key key, final Provider keyP
693687
Assert.stateNotNull(keyAlgFunction, "KeyAlgorithm function cannot be null.");
694688
assertPayloadEncoding("JWE");
695689

696-
InputStream plaintext;
697-
if (content.isClaims()) {
698-
ByteArrayOutputStream out = new ByteArrayOutputStream(4096);
699-
writeAndClose("JWE Claims", content, out);
700-
plaintext = Streams.of(out.toByteArray());
701-
} else {
702-
plaintext = content.toInputStream();
703-
}
690+
InputStream plaintext = toInputStream("JWE Payload", content);
704691

705692
//only expose (mutable) JweHeader functionality to KeyAlgorithm instances, not the full headerBuilder
706693
// (which exposes this JwtBuilder and shouldn't be referenced by KeyAlgorithms):
@@ -820,4 +807,15 @@ private void encodeAndWrite(String name, byte[] data, OutputStream out) {
820807
Streams.writeAndClose(out, data, "Unable to write bytes");
821808
}
822809

810+
private InputStream toInputStream(final String name, Payload payload) {
811+
if (payload.isClaims() || payload.isCompressed()) {
812+
ByteArrayOutputStream claimsOut = new ByteArrayOutputStream(8192);
813+
writeAndClose(name, payload, claimsOut);
814+
return Streams.of(claimsOut.toByteArray());
815+
} else {
816+
// No claims and not compressed, so just get the direct InputStream:
817+
return Assert.stateNotNull(payload.toInputStream(), "Payload InputStream cannot be null.");
818+
}
819+
}
820+
823821
}

impl/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy

+92
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import io.jsonwebtoken.impl.io.Streams
2222
import io.jsonwebtoken.impl.lang.Bytes
2323
import io.jsonwebtoken.impl.lang.Services
2424
import io.jsonwebtoken.impl.security.*
25+
import io.jsonwebtoken.io.CompressionAlgorithm
2526
import io.jsonwebtoken.io.Decoders
2627
import io.jsonwebtoken.io.Deserializer
2728
import io.jsonwebtoken.io.Encoders
@@ -1398,6 +1399,97 @@ class JwtsTest {
13981399
}
13991400
}
14001401

1402+
@Test
1403+
void testJweCompressionWithArbitraryContentString() {
1404+
def codecs = [Jwts.ZIP.DEF, Jwts.ZIP.GZIP]
1405+
1406+
for (CompressionAlgorithm zip : codecs) {
1407+
1408+
for (AeadAlgorithm enc : Jwts.ENC.get().values()) {
1409+
1410+
SecretKey key = enc.key().build()
1411+
1412+
String payload = 'hello, world!'
1413+
1414+
// encrypt and compress:
1415+
String jwe = Jwts.builder()
1416+
.content(payload)
1417+
.compressWith(zip)
1418+
.encryptWith(key, enc)
1419+
.compact()
1420+
1421+
//decompress and decrypt:
1422+
def jwt = Jwts.parser()
1423+
.decryptWith(key)
1424+
.build()
1425+
.parseEncryptedContent(jwe)
1426+
assertEquals payload, new String(jwt.getPayload(), StandardCharsets.UTF_8)
1427+
}
1428+
}
1429+
}
1430+
1431+
@Test
1432+
void testJweCompressionWithArbitraryContentByteArray() {
1433+
def codecs = [Jwts.ZIP.DEF, Jwts.ZIP.GZIP]
1434+
1435+
for (CompressionAlgorithm zip : codecs) {
1436+
1437+
for (AeadAlgorithm enc : Jwts.ENC.get().values()) {
1438+
1439+
SecretKey key = enc.key().build()
1440+
1441+
byte[] payload = new byte[14];
1442+
Randoms.secureRandom().nextBytes(payload)
1443+
1444+
// encrypt and compress:
1445+
String jwe = Jwts.builder()
1446+
.content(payload)
1447+
.compressWith(zip)
1448+
.encryptWith(key, enc)
1449+
.compact()
1450+
1451+
//decompress and decrypt:
1452+
def jwt = Jwts.parser()
1453+
.decryptWith(key)
1454+
.build()
1455+
.parseEncryptedContent(jwe)
1456+
assertArrayEquals payload, jwt.getPayload()
1457+
}
1458+
}
1459+
}
1460+
1461+
@Test
1462+
void testJweCompressionWithArbitraryContentInputStream() {
1463+
def codecs = [Jwts.ZIP.DEF, Jwts.ZIP.GZIP]
1464+
1465+
for (CompressionAlgorithm zip : codecs) {
1466+
1467+
for (AeadAlgorithm enc : Jwts.ENC.get().values()) {
1468+
1469+
SecretKey key = enc.key().build()
1470+
1471+
byte[] payloadBytes = new byte[14];
1472+
Randoms.secureRandom().nextBytes(payloadBytes)
1473+
1474+
ByteArrayInputStream payload = new ByteArrayInputStream(payloadBytes)
1475+
1476+
// encrypt and compress:
1477+
String jwe = Jwts.builder()
1478+
.content(payload)
1479+
.compressWith(zip)
1480+
.encryptWith(key, enc)
1481+
.compact()
1482+
1483+
//decompress and decrypt:
1484+
def jwt = Jwts.parser()
1485+
.decryptWith(key)
1486+
.build()
1487+
.parseEncryptedContent(jwe)
1488+
assertArrayEquals payloadBytes, jwt.getPayload()
1489+
}
1490+
}
1491+
}
1492+
14011493
@Test
14021494
void testPasswordJwes() {
14031495

0 commit comments

Comments
 (0)