Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JWE arbitrary content compression #937

Merged

Conversation

mnylen
Copy link
Contributor

@mnylen mnylen commented Apr 22, 2024

The JWE content was compressed only when claims was used as payload, but when using arbitrary content string/bytes/input stream, the compression was omitted, while keeping the "zip" header field, leading to un-parseable JWE as the decompression failed on non-compressed content.

I couldn't find any evidence that this was intended behavior from the documentation or the JWE spec, so made a fix for this.

See #936

Note that people might have used a similar workaround as I presented in the discussion to manually compress the payload, so fixing this might result in some code breaking somewhere. Might be good to mention in the changelog.

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.
@mnylen mnylen force-pushed the allow-compress-decompress-for-arbitrary-content branch from 700a92a to c83fbe9 Compare April 22, 2024 05:23
@lhazlewood
Copy link
Contributor

Excellent find!!! I can't believe this was missed, thank you so much 🙏

} else {
plaintext = content.toInputStream();
writeAndClose("JWE Content", content, out);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have to be careful here: if compression is not enabled, we don't want to write the plaintext Inputstream to an intermediate output stream (and then converted back into an input stream) which would be an unnecessary roundtrip/copy.

This logic should look more like that found in the sign method:

if (payload.isClaims() || payload.isCompressed()) {
ByteArrayOutputStream claimsOut = new ByteArrayOutputStream(8192);
writeAndClose("JWS Unencoded Payload", payload, claimsOut);
payloadStream = Streams.of(claimsOut.toByteArray());
} else {
// No claims and not compressed, so just get the direct InputStream:
payloadStream = Assert.stateNotNull(payload.toInputStream(), "Payload InputStream cannot be null.");
}

If possible, it'd be ideal if we can move this logic to a helper method that is shared by both sign and encrypt

Copy link
Contributor Author

@mnylen mnylen Apr 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense.

I wonder though if we should also be a bit more clever with the initial byte array size for ByteArrayOutputStream. Seems a bit wasteful to do 8 KB byte array allocation if the content is only few hundred bytes. At least if the payload has byte array / string, we could use the initial payload size as the starting point, as even with compression, we shouldn't (at least usually) go above that size.

Edit: Well, maybe I'll try to make a different PR about the initial sizes later. Noticed that writeAndClose also creates a 4KB buffer, when it could also just use the initial payload size as the starting point.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@lhazlewood lhazlewood changed the title Fix compression being omitted for JWEs with arbitrary content JWE arbitrary content compression Apr 22, 2024
@lhazlewood lhazlewood added this to the 0.12.6 milestone Apr 22, 2024
// No claims and not compressed, so just get the direct InputStream:
payloadStream = Assert.stateNotNull(payload.toInputStream(), "Payload InputStream cannot be null.");
}
payloadStream = convertPayloadToInputStream(payload);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to retain the existing names in exception messages so we don't lose context, so I think the helper method needs to also accept a String identifying what is being converted (e.g. JWS Unencoded Payload, JWE Claims), e.g.

private InputStream toInputStream(Payload payload, String name) {
    ...
}

(also probably don't need the word Payload in the method name since it's redundant given it only accepts a Payload data type).

Copy link
Contributor

@lhazlewood lhazlewood left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a minor change remaining, and then I think we can merge. Thanks for your continued help!

@lhazlewood lhazlewood merged commit 3489fdb into jwtk:master Apr 25, 2024
24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants