This is an automated email from the ASF dual-hosted git repository.
coheigea pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cxf.git
The following commit(s) were added to refs/heads/main by this push:
new 9874f196dd4 verify jws and jwe bodies sent with DELETE and other
bodyless methods (#3670)
9874f196dd4 is described below
commit 9874f196dd420d4599d52c9cb4190c0f020c86a6
Author: Naveed Khan <[email protected]>
AuthorDate: Fri Nov 6 13:26:42 2026 +0530
verify jws and jwe bodies sent with DELETE and other bodyless methods
(#3670)
* verify jws and jwe bodies sent with DELETE and other bodyless methods
The JOSE request filters returned before reading the entity for DELETE,
GET, HEAD, OPTIONS and TRACE, and the client response filters did the
same for responses to DELETE, HEAD and OPTIONS, so a body sent with
those methods was never verified or decrypted. Skip them only when
there is no entity.
* read the stream instead of hasEntity in the jose filters
isMethodWithNoContent now returns false by default and lives in the four
abstract base classes, so every filter reads the entity whatever the
method and relies on the existing empty-content return. Subclasses that
override it keep its old meaning: the method is skipped entirely.
The client tests use the real ClientRequestContextImpl and
ClientResponseContextImpl, every one of the eight filters has a test,
and a DELETE body sent with Content-Length: 0 is still verified.
* skip a missing entity stream and stop trusting Content-Length: 0 in the
jose filters
ContainerRequestContext.getEntityStream() and ClientResponseContext
.getEntityStream() can return null, for example for a GET over the local
transport or a response without an entity, and IOUtils rejects a null
stream. All eight filters now return when there is no stream.
checkEmptyStream went through hasEntity(), which returns false on a
Content-Length: 0 header without looking at the stream. The filters
already read the stream and return on empty content, so the hasEntity()
shortcut is dropped and the setting no longer has any effect.
The isMethodWithNoContent Javadoc now spells out the old default for
subclasses that extend it through super.
* put an empty stream back after an empty jose body and deprecate
checkEmptyStream
IOUtils.readStringFromStream and readBytesFromStream close the stream
they read, and the filters returned on empty content without setting a
new one, so every bodyless GET or DELETE left a closed servlet stream on
the message (and an empty client response left a closed entity stream).
All eight filters now set an empty ByteArrayInputStream before returning.
The checkEmptyStream accessors are marked @Deprecated, as the flag no
longer has any effect.
The test request and response streams now fail once closed, as a
container stream does, and each filter has a test that reads the entity
after filtering an empty GET request or DELETE response.
---
rt/rs/security/jose-parent/jose-jaxrs/pom.xml | 6 +
.../jose/jaxrs/AbstractJweDecryptingFilter.java | 28 +++
.../jaxrs/AbstractJweJsonDecryptingFilter.java | 28 +++
.../jose/jaxrs/AbstractJwsJsonReaderProvider.java | 28 +++
.../jose/jaxrs/AbstractJwsReaderProvider.java | 28 +++
.../jose/jaxrs/JweClientResponseFilter.java | 13 +-
.../jose/jaxrs/JweContainerRequestFilter.java | 14 +-
.../jose/jaxrs/JweJsonClientResponseFilter.java | 13 +-
.../jose/jaxrs/JweJsonContainerRequestFilter.java | 15 +-
.../jose/jaxrs/JwsClientResponseFilter.java | 13 +-
.../jose/jaxrs/JwsContainerRequestFilter.java | 14 +-
.../jose/jaxrs/JwsJsonClientResponseFilter.java | 13 +-
.../jose/jaxrs/JwsJsonContainerRequestFilter.java | 14 +-
.../security/jose/jaxrs/JoseFilterTestSupport.java | 195 +++++++++++++++++++++
.../jose/jaxrs/JweClientResponseFilterTest.java | 80 +++++++++
.../jose/jaxrs/JweContainerRequestFilterTest.java | 81 +++++++++
.../jaxrs/JweJsonClientResponseFilterTest.java | 83 +++++++++
.../jaxrs/JweJsonContainerRequestFilterTest.java | 86 +++++++++
.../jose/jaxrs/JwsClientResponseFilterTest.java | 111 ++++++++++++
.../jose/jaxrs/JwsContainerRequestFilterTest.java | 127 ++++++++++++++
.../jaxrs/JwsJsonClientResponseFilterTest.java | 97 ++++++++++
.../jaxrs/JwsJsonContainerRequestFilterTest.java | 74 ++++++++
22 files changed, 1092 insertions(+), 69 deletions(-)
diff --git a/rt/rs/security/jose-parent/jose-jaxrs/pom.xml
b/rt/rs/security/jose-parent/jose-jaxrs/pom.xml
index 2b515af6f1a..7da927eb740 100644
--- a/rt/rs/security/jose-parent/jose-jaxrs/pom.xml
+++ b/rt/rs/security/jose-parent/jose-jaxrs/pom.xml
@@ -64,5 +64,11 @@
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-rt-rs-client</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</project>
diff --git
a/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/AbstractJweDecryptingFilter.java
b/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/AbstractJweDecryptingFilter.java
index e3ab5d1c042..6b4f2466868 100644
---
a/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/AbstractJweDecryptingFilter.java
+++
b/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/AbstractJweDecryptingFilter.java
@@ -82,12 +82,40 @@ public class AbstractJweDecryptingFilter {
this.protectedHttpHeaders = protectedHttpHeaders;
}
+ /**
+ * @deprecated has no effect, see {@link #setCheckEmptyStream(boolean)}.
+ */
+ @Deprecated
public boolean isCheckEmptyStream() {
return checkEmptyStream;
}
+ /**
+ * No longer has any effect: the filters always read the entity stream and
skip
+ * a missing or empty one. A Content-Length: 0 header alone is not
trusted, as the
+ * stream may still carry a body.
+ *
+ * @deprecated has no effect.
+ */
+ @Deprecated
public void setCheckEmptyStream(boolean checkEmptyStream) {
this.checkEmptyStream = checkEmptyStream;
}
+
+ /**
+ * Returns true if the entity must not be read for the given HTTP method.
+ * By default no method is skipped: an entity is always decrypted when one
is
+ * present, whatever the method, as JAX-RS binds an entity parameter for
+ * DELETE, GET and the other methods too. Subclasses may override this
+ * to skip specific methods entirely.
+ * <p>
+ * This used to return true for DELETE, GET, HEAD, OPTIONS and TRACE
requests and
+ * for responses to DELETE, HEAD and OPTIONS. An override that extends
+ * super.isMethodWithNoContent and still needs those methods skipped
entirely
+ * has to list them itself.
+ */
+ protected boolean isMethodWithNoContent(String method) {
+ return false;
+ }
}
diff --git
a/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/AbstractJweJsonDecryptingFilter.java
b/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/AbstractJweJsonDecryptingFilter.java
index 5f78870d30f..fd74808f9af 100644
---
a/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/AbstractJweJsonDecryptingFilter.java
+++
b/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/AbstractJweJsonDecryptingFilter.java
@@ -94,11 +94,39 @@ public class AbstractJweJsonDecryptingFilter {
this.protectedHttpHeaders = protectedHttpHeaders;
}
+ /**
+ * @deprecated has no effect, see {@link #setCheckEmptyStream(boolean)}.
+ */
+ @Deprecated
public boolean isCheckEmptyStream() {
return checkEmptyStream;
}
+ /**
+ * No longer has any effect: the filters always read the entity stream and
skip
+ * a missing or empty one. A Content-Length: 0 header alone is not
trusted, as the
+ * stream may still carry a body.
+ *
+ * @deprecated has no effect.
+ */
+ @Deprecated
public void setCheckEmptyStream(boolean checkEmptyStream) {
this.checkEmptyStream = checkEmptyStream;
}
+
+ /**
+ * Returns true if the entity must not be read for the given HTTP method.
+ * By default no method is skipped: an entity is always decrypted when one
is
+ * present, whatever the method, as JAX-RS binds an entity parameter for
+ * DELETE, GET and the other methods too. Subclasses may override this
+ * to skip specific methods entirely.
+ * <p>
+ * This used to return true for DELETE, GET, HEAD, OPTIONS and TRACE
requests and
+ * for responses to DELETE, HEAD and OPTIONS. An override that extends
+ * super.isMethodWithNoContent and still needs those methods skipped
entirely
+ * has to list them itself.
+ */
+ protected boolean isMethodWithNoContent(String method) {
+ return false;
+ }
}
diff --git
a/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/AbstractJwsJsonReaderProvider.java
b/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/AbstractJwsJsonReaderProvider.java
index e55c163fc86..92760770675 100644
---
a/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/AbstractJwsJsonReaderProvider.java
+++
b/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/AbstractJwsJsonReaderProvider.java
@@ -112,13 +112,41 @@ public class AbstractJwsJsonReaderProvider {
this.protectedHttpHeaders = protectedHttpHeaders;
}
+ /**
+ * @deprecated has no effect, see {@link #setCheckEmptyStream(boolean)}.
+ */
+ @Deprecated
public boolean isCheckEmptyStream() {
return checkEmptyStream;
}
+ /**
+ * No longer has any effect: the filters always read the entity stream and
skip
+ * a missing or empty one. A Content-Length: 0 header alone is not
trusted, as the
+ * stream may still carry a body.
+ *
+ * @deprecated has no effect.
+ */
+ @Deprecated
public void setCheckEmptyStream(boolean checkEmptyStream) {
this.checkEmptyStream = checkEmptyStream;
}
+ /**
+ * Returns true if the entity must not be read for the given HTTP method.
+ * By default no method is skipped: an entity is always verified when one
is
+ * present, whatever the method, as JAX-RS binds an entity parameter for
+ * DELETE, GET and the other methods too. Subclasses may override this
+ * to skip specific methods entirely.
+ * <p>
+ * This used to return true for DELETE, GET, HEAD, OPTIONS and TRACE
requests and
+ * for responses to DELETE, HEAD and OPTIONS. An override that extends
+ * super.isMethodWithNoContent and still needs those methods skipped
entirely
+ * has to list them itself.
+ */
+ protected boolean isMethodWithNoContent(String method) {
+ return false;
+ }
+
}
diff --git
a/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/AbstractJwsReaderProvider.java
b/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/AbstractJwsReaderProvider.java
index d4da400e1f7..37e93f66c68 100644
---
a/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/AbstractJwsReaderProvider.java
+++
b/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/AbstractJwsReaderProvider.java
@@ -70,12 +70,40 @@ public class AbstractJwsReaderProvider {
this.protectedHttpHeaders = protectedHttpHeaders;
}
+ /**
+ * @deprecated has no effect, see {@link #setCheckEmptyStream(boolean)}.
+ */
+ @Deprecated
public boolean isCheckEmptyStream() {
return checkEmptyStream;
}
+ /**
+ * No longer has any effect: the filters always read the entity stream and
skip
+ * a missing or empty one. A Content-Length: 0 header alone is not
trusted, as the
+ * stream may still carry a body.
+ *
+ * @deprecated has no effect.
+ */
+ @Deprecated
public void setCheckEmptyStream(boolean checkEmptyStream) {
this.checkEmptyStream = checkEmptyStream;
}
+
+ /**
+ * Returns true if the entity must not be read for the given HTTP method.
+ * By default no method is skipped: an entity is always verified when one
is
+ * present, whatever the method, as JAX-RS binds an entity parameter for
+ * DELETE, GET and the other methods too. Subclasses may override this
+ * to skip specific methods entirely.
+ * <p>
+ * This used to return true for DELETE, GET, HEAD, OPTIONS and TRACE
requests and
+ * for responses to DELETE, HEAD and OPTIONS. An override that extends
+ * super.isMethodWithNoContent and still needs those methods skipped
entirely
+ * has to list them itself.
+ */
+ protected boolean isMethodWithNoContent(String method) {
+ return false;
+ }
}
diff --git
a/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JweClientResponseFilter.java
b/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JweClientResponseFilter.java
index 00930e2d380..1bba964f6a4 100644
---
a/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JweClientResponseFilter.java
+++
b/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JweClientResponseFilter.java
@@ -20,15 +20,14 @@ package org.apache.cxf.rs.security.jose.jaxrs;
import java.io.ByteArrayInputStream;
import java.io.IOException;
+import java.io.InputStream;
import jakarta.annotation.Priority;
-import jakarta.ws.rs.HttpMethod;
import jakarta.ws.rs.client.ClientRequestContext;
import jakarta.ws.rs.client.ClientResponseContext;
import jakarta.ws.rs.client.ClientResponseFilter;
import jakarta.ws.rs.core.Response;
import org.apache.cxf.helpers.IOUtils;
-import org.apache.cxf.jaxrs.utils.HttpUtils;
import org.apache.cxf.rs.security.jose.common.JoseUtils;
import org.apache.cxf.rs.security.jose.jwe.JweDecryptionOutput;
@@ -36,13 +35,15 @@ import
org.apache.cxf.rs.security.jose.jwe.JweDecryptionOutput;
public class JweClientResponseFilter extends AbstractJweDecryptingFilter
implements ClientResponseFilter {
@Override
public void filter(ClientRequestContext req, ClientResponseContext res)
throws IOException {
+ final InputStream entityStream = res.getEntityStream();
if (isMethodWithNoContent(req.getMethod())
|| isStatusCodeWithNoContent(res.getStatus())
- || isCheckEmptyStream() && !res.hasEntity()) {
+ || entityStream == null) {
return;
}
- final byte[] encryptedContent =
IOUtils.readBytesFromStream(res.getEntityStream());
+ final byte[] encryptedContent =
IOUtils.readBytesFromStream(entityStream);
if (encryptedContent.length == 0) {
+ res.setEntityStream(new ByteArrayInputStream(new byte[0]));
return;
}
JweDecryptionOutput out = decrypt(encryptedContent);
@@ -58,10 +59,6 @@ public class JweClientResponseFilter extends
AbstractJweDecryptingFilter impleme
}
}
- protected boolean isMethodWithNoContent(String method) {
- return HttpMethod.DELETE.equals(method) ||
HttpUtils.isMethodWithNoResponseContent(method);
- }
-
protected boolean isStatusCodeWithNoContent(int statusCode) {
return statusCode == Response.Status.NO_CONTENT.getStatusCode();
}
diff --git
a/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JweContainerRequestFilter.java
b/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JweContainerRequestFilter.java
index 25fea663e65..353512adac1 100644
---
a/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JweContainerRequestFilter.java
+++
b/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JweContainerRequestFilter.java
@@ -20,14 +20,13 @@ package org.apache.cxf.rs.security.jose.jaxrs;
import java.io.ByteArrayInputStream;
import java.io.IOException;
+import java.io.InputStream;
import jakarta.annotation.Priority;
-import jakarta.ws.rs.HttpMethod;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.container.PreMatching;
import org.apache.cxf.helpers.IOUtils;
-import org.apache.cxf.jaxrs.utils.HttpUtils;
import org.apache.cxf.rs.security.jose.common.JoseUtils;
import org.apache.cxf.rs.security.jose.jwe.JweDecryptionOutput;
@@ -36,12 +35,13 @@ import
org.apache.cxf.rs.security.jose.jwe.JweDecryptionOutput;
public class JweContainerRequestFilter extends AbstractJweDecryptingFilter
implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext context) throws IOException {
- if (isMethodWithNoContent(context.getMethod())
- || isCheckEmptyStream() && !context.hasEntity()) {
+ final InputStream entityStream = context.getEntityStream();
+ if (isMethodWithNoContent(context.getMethod()) || entityStream ==
null) {
return;
}
- final byte[] encryptedContent =
IOUtils.readBytesFromStream(context.getEntityStream());
+ final byte[] encryptedContent =
IOUtils.readBytesFromStream(entityStream);
if (encryptedContent.length == 0) {
+ context.setEntityStream(new ByteArrayInputStream(new byte[0]));
return;
}
JweDecryptionOutput out = decrypt(encryptedContent);
@@ -56,8 +56,4 @@ public class JweContainerRequestFilter extends
AbstractJweDecryptingFilter imple
super.validateHttpHeadersIfNeeded(context.getHeaders(),
out.getHeaders());
}
}
-
- protected boolean isMethodWithNoContent(String method) {
- return HttpMethod.DELETE.equals(method) ||
HttpUtils.isMethodWithNoRequestContent(method);
- }
}
diff --git
a/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JweJsonClientResponseFilter.java
b/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JweJsonClientResponseFilter.java
index 4c509ff5765..695206de163 100644
---
a/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JweJsonClientResponseFilter.java
+++
b/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JweJsonClientResponseFilter.java
@@ -20,15 +20,14 @@ package org.apache.cxf.rs.security.jose.jaxrs;
import java.io.ByteArrayInputStream;
import java.io.IOException;
+import java.io.InputStream;
import jakarta.annotation.Priority;
-import jakarta.ws.rs.HttpMethod;
import jakarta.ws.rs.client.ClientRequestContext;
import jakarta.ws.rs.client.ClientResponseContext;
import jakarta.ws.rs.client.ClientResponseFilter;
import jakarta.ws.rs.core.Response;
import org.apache.cxf.helpers.IOUtils;
-import org.apache.cxf.jaxrs.utils.HttpUtils;
import org.apache.cxf.rs.security.jose.common.JoseUtils;
import org.apache.cxf.rs.security.jose.jwe.JweDecryptionOutput;
@@ -36,13 +35,15 @@ import
org.apache.cxf.rs.security.jose.jwe.JweDecryptionOutput;
public class JweJsonClientResponseFilter extends
AbstractJweJsonDecryptingFilter implements ClientResponseFilter {
@Override
public void filter(ClientRequestContext req, ClientResponseContext res)
throws IOException {
+ final InputStream entityStream = res.getEntityStream();
if (isMethodWithNoContent(req.getMethod())
|| isStatusCodeWithNoContent(res.getStatus())
- || isCheckEmptyStream() && !res.hasEntity()) {
+ || entityStream == null) {
return;
}
- final byte[] encryptedContent =
IOUtils.readBytesFromStream(res.getEntityStream());
+ final byte[] encryptedContent =
IOUtils.readBytesFromStream(entityStream);
if (encryptedContent.length == 0) {
+ res.setEntityStream(new ByteArrayInputStream(new byte[0]));
return;
}
JweDecryptionOutput out = decrypt(encryptedContent);
@@ -58,10 +59,6 @@ public class JweJsonClientResponseFilter extends
AbstractJweJsonDecryptingFilter
}
}
- protected boolean isMethodWithNoContent(String method) {
- return HttpMethod.DELETE.equals(method) ||
HttpUtils.isMethodWithNoResponseContent(method);
- }
-
protected boolean isStatusCodeWithNoContent(int statusCode) {
return statusCode == Response.Status.NO_CONTENT.getStatusCode();
}
diff --git
a/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JweJsonContainerRequestFilter.java
b/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JweJsonContainerRequestFilter.java
index 1b11d12aea4..a5c3a5d4312 100644
---
a/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JweJsonContainerRequestFilter.java
+++
b/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JweJsonContainerRequestFilter.java
@@ -20,14 +20,13 @@ package org.apache.cxf.rs.security.jose.jaxrs;
import java.io.ByteArrayInputStream;
import java.io.IOException;
+import java.io.InputStream;
import jakarta.annotation.Priority;
-import jakarta.ws.rs.HttpMethod;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.container.PreMatching;
import org.apache.cxf.helpers.IOUtils;
-import org.apache.cxf.jaxrs.utils.HttpUtils;
import org.apache.cxf.jaxrs.utils.JAXRSUtils;
import org.apache.cxf.rs.security.jose.common.JoseUtils;
import org.apache.cxf.rs.security.jose.jwe.JweDecryptionOutput;
@@ -38,12 +37,13 @@ import org.apache.cxf.rs.security.jose.jwe.JweException;
public class JweJsonContainerRequestFilter extends
AbstractJweJsonDecryptingFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext context) throws IOException {
- if (isMethodWithNoContent(context.getMethod())
- || isCheckEmptyStream() && !context.hasEntity()) {
+ final InputStream entityStream = context.getEntityStream();
+ if (isMethodWithNoContent(context.getMethod()) || entityStream ==
null) {
return;
}
- final byte[] encryptedContent =
IOUtils.readBytesFromStream(context.getEntityStream());
+ final byte[] encryptedContent =
IOUtils.readBytesFromStream(entityStream);
if (encryptedContent.length == 0) {
+ context.setEntityStream(new ByteArrayInputStream(new byte[0]));
return;
}
try {
@@ -63,9 +63,4 @@ public class JweJsonContainerRequestFilter extends
AbstractJweJsonDecryptingFilt
return;
}
}
-
- protected boolean isMethodWithNoContent(String method) {
- return HttpMethod.DELETE.equals(method) ||
HttpUtils.isMethodWithNoRequestContent(method);
- }
-
}
diff --git
a/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JwsClientResponseFilter.java
b/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JwsClientResponseFilter.java
index 80e21297d17..168eceb3d00 100644
---
a/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JwsClientResponseFilter.java
+++
b/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JwsClientResponseFilter.java
@@ -20,16 +20,15 @@ package org.apache.cxf.rs.security.jose.jaxrs;
import java.io.ByteArrayInputStream;
import java.io.IOException;
+import java.io.InputStream;
import jakarta.annotation.Priority;
-import jakarta.ws.rs.HttpMethod;
import jakarta.ws.rs.client.ClientRequestContext;
import jakarta.ws.rs.client.ClientResponseContext;
import jakarta.ws.rs.client.ClientResponseFilter;
import jakarta.ws.rs.core.Response;
import org.apache.cxf.common.util.StringUtils;
import org.apache.cxf.helpers.IOUtils;
-import org.apache.cxf.jaxrs.utils.HttpUtils;
import org.apache.cxf.rs.security.jose.common.JoseUtils;
import org.apache.cxf.rs.security.jose.jws.JwsCompactConsumer;
import org.apache.cxf.rs.security.jose.jws.JwsException;
@@ -39,13 +38,15 @@ import
org.apache.cxf.rs.security.jose.jws.JwsSignatureVerifier;
public class JwsClientResponseFilter extends AbstractJwsReaderProvider
implements ClientResponseFilter {
@Override
public void filter(ClientRequestContext req, ClientResponseContext res)
throws IOException {
+ final InputStream entityStream = res.getEntityStream();
if (isMethodWithNoContent(req.getMethod())
|| isStatusCodeWithNoContent(res.getStatus())
- || isCheckEmptyStream() && !res.hasEntity()) {
+ || entityStream == null) {
return;
}
- final String content =
IOUtils.readStringFromStream(res.getEntityStream());
+ final String content = IOUtils.readStringFromStream(entityStream);
if (StringUtils.isEmpty(content)) {
+ res.setEntityStream(new ByteArrayInputStream(new byte[0]));
return;
}
JwsCompactConsumer p = new JwsCompactConsumer(content);
@@ -67,10 +68,6 @@ public class JwsClientResponseFilter extends
AbstractJwsReaderProvider implement
}
}
- protected boolean isMethodWithNoContent(String method) {
- return HttpMethod.DELETE.equals(method) ||
HttpUtils.isMethodWithNoResponseContent(method);
- }
-
protected boolean isStatusCodeWithNoContent(int statusCode) {
return statusCode == Response.Status.NO_CONTENT.getStatusCode();
}
diff --git
a/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JwsContainerRequestFilter.java
b/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JwsContainerRequestFilter.java
index 337e874350b..7df57031fc1 100644
---
a/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JwsContainerRequestFilter.java
+++
b/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JwsContainerRequestFilter.java
@@ -20,16 +20,15 @@ package org.apache.cxf.rs.security.jose.jaxrs;
import java.io.ByteArrayInputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.security.Principal;
import jakarta.annotation.Priority;
-import jakarta.ws.rs.HttpMethod;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.container.PreMatching;
import org.apache.cxf.common.util.StringUtils;
import org.apache.cxf.helpers.IOUtils;
-import org.apache.cxf.jaxrs.utils.HttpUtils;
import org.apache.cxf.jaxrs.utils.JAXRSUtils;
import org.apache.cxf.rs.security.jose.common.JoseUtils;
import org.apache.cxf.rs.security.jose.jws.JwsCompactConsumer;
@@ -42,12 +41,13 @@ import org.apache.cxf.security.SecurityContext;
public class JwsContainerRequestFilter extends AbstractJwsReaderProvider
implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext context) throws IOException {
- if (isMethodWithNoContent(context.getMethod())
- || isCheckEmptyStream() && !context.hasEntity()) {
+ final InputStream entityStream = context.getEntityStream();
+ if (isMethodWithNoContent(context.getMethod()) || entityStream ==
null) {
return;
}
- final String content =
IOUtils.readStringFromStream(context.getEntityStream());
+ final String content = IOUtils.readStringFromStream(entityStream);
if (StringUtils.isEmpty(content)) {
+ context.setEntityStream(new ByteArrayInputStream(new byte[0]));
return;
}
JwsCompactConsumer p = new JwsCompactConsumer(content);
@@ -98,8 +98,4 @@ public class JwsContainerRequestFilter extends
AbstractJwsReaderProvider impleme
}
return null;
}
-
- protected boolean isMethodWithNoContent(String method) {
- return HttpMethod.DELETE.equals(method) ||
HttpUtils.isMethodWithNoRequestContent(method);
- }
}
diff --git
a/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JwsJsonClientResponseFilter.java
b/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JwsJsonClientResponseFilter.java
index 794291aa05b..0442b191eed 100644
---
a/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JwsJsonClientResponseFilter.java
+++
b/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JwsJsonClientResponseFilter.java
@@ -20,16 +20,15 @@ package org.apache.cxf.rs.security.jose.jaxrs;
import java.io.ByteArrayInputStream;
import java.io.IOException;
+import java.io.InputStream;
import jakarta.annotation.Priority;
-import jakarta.ws.rs.HttpMethod;
import jakarta.ws.rs.client.ClientRequestContext;
import jakarta.ws.rs.client.ClientResponseContext;
import jakarta.ws.rs.client.ClientResponseFilter;
import jakarta.ws.rs.core.Response;
import org.apache.cxf.common.util.StringUtils;
import org.apache.cxf.helpers.IOUtils;
-import org.apache.cxf.jaxrs.utils.HttpUtils;
import org.apache.cxf.rs.security.jose.common.JoseUtils;
import org.apache.cxf.rs.security.jose.jws.JwsJsonConsumer;
import org.apache.cxf.rs.security.jose.jws.JwsJsonSignatureEntry;
@@ -39,13 +38,15 @@ import
org.apache.cxf.rs.security.jose.jws.JwsSignatureVerifier;
public class JwsJsonClientResponseFilter extends AbstractJwsJsonReaderProvider
implements ClientResponseFilter {
@Override
public void filter(ClientRequestContext req, ClientResponseContext res)
throws IOException {
+ final InputStream entityStream = res.getEntityStream();
if (isMethodWithNoContent(req.getMethod())
|| isStatusCodeWithNoContent(res.getStatus())
- || isCheckEmptyStream() && !res.hasEntity()) {
+ || entityStream == null) {
return;
}
- final String content =
IOUtils.readStringFromStream(res.getEntityStream());
+ final String content = IOUtils.readStringFromStream(entityStream);
if (StringUtils.isEmpty(content)) {
+ res.setEntityStream(new ByteArrayInputStream(new byte[0]));
return;
}
JwsSignatureVerifier theSigVerifier = getInitializedSigVerifier();
@@ -62,10 +63,6 @@ public class JwsJsonClientResponseFilter extends
AbstractJwsJsonReaderProvider i
}
}
- protected boolean isMethodWithNoContent(String method) {
- return HttpMethod.DELETE.equals(method) ||
HttpUtils.isMethodWithNoResponseContent(method);
- }
-
protected boolean isStatusCodeWithNoContent(int statusCode) {
return statusCode == Response.Status.NO_CONTENT.getStatusCode();
}
diff --git
a/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JwsJsonContainerRequestFilter.java
b/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JwsJsonContainerRequestFilter.java
index 2dde1824b77..fb6244f4820 100644
---
a/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JwsJsonContainerRequestFilter.java
+++
b/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JwsJsonContainerRequestFilter.java
@@ -20,15 +20,14 @@ package org.apache.cxf.rs.security.jose.jaxrs;
import java.io.ByteArrayInputStream;
import java.io.IOException;
+import java.io.InputStream;
import jakarta.annotation.Priority;
-import jakarta.ws.rs.HttpMethod;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.container.PreMatching;
import org.apache.cxf.common.util.StringUtils;
import org.apache.cxf.helpers.IOUtils;
-import org.apache.cxf.jaxrs.utils.HttpUtils;
import org.apache.cxf.jaxrs.utils.JAXRSUtils;
import org.apache.cxf.rs.security.jose.common.JoseUtils;
import org.apache.cxf.rs.security.jose.jws.JwsException;
@@ -41,12 +40,13 @@ import
org.apache.cxf.rs.security.jose.jws.JwsSignatureVerifier;
public class JwsJsonContainerRequestFilter extends
AbstractJwsJsonReaderProvider implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext context) throws IOException {
- if (isMethodWithNoContent(context.getMethod())
- || isCheckEmptyStream() && !context.hasEntity()) {
+ final InputStream entityStream = context.getEntityStream();
+ if (isMethodWithNoContent(context.getMethod()) || entityStream ==
null) {
return;
}
- final String content =
IOUtils.readStringFromStream(context.getEntityStream());
+ final String content = IOUtils.readStringFromStream(entityStream);
if (StringUtils.isEmpty(content)) {
+ context.setEntityStream(new ByteArrayInputStream(new byte[0]));
return;
}
JwsSignatureVerifier theSigVerifier = getInitializedSigVerifier();
@@ -71,8 +71,4 @@ public class JwsJsonContainerRequestFilter extends
AbstractJwsJsonReaderProvider
super.validateHttpHeadersIfNeeded(context.getHeaders(),
sigEntry.getProtectedHeader());
}
}
-
- protected boolean isMethodWithNoContent(String method) {
- return HttpMethod.DELETE.equals(method) ||
HttpUtils.isMethodWithNoRequestContent(method);
- }
}
diff --git
a/rt/rs/security/jose-parent/jose-jaxrs/src/test/java/org/apache/cxf/rs/security/jose/jaxrs/JoseFilterTestSupport.java
b/rt/rs/security/jose-parent/jose-jaxrs/src/test/java/org/apache/cxf/rs/security/jose/jaxrs/JoseFilterTestSupport.java
new file mode 100644
index 00000000000..afc4f08dd63
--- /dev/null
+++
b/rt/rs/security/jose-parent/jose-jaxrs/src/test/java/org/apache/cxf/rs/security/jose/jaxrs/JoseFilterTestSupport.java
@@ -0,0 +1,195 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.rs.security.jose.jaxrs;
+
+import java.io.ByteArrayInputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import jakarta.ws.rs.client.ClientRequestContext;
+import jakarta.ws.rs.client.ClientResponseContext;
+import jakarta.ws.rs.core.HttpHeaders;
+import org.apache.cxf.bus.managers.PhaseManagerImpl;
+import org.apache.cxf.interceptor.Fault;
+import org.apache.cxf.jaxrs.client.spec.ClientRequestContextImpl;
+import org.apache.cxf.jaxrs.client.spec.ClientResponseContextImpl;
+import org.apache.cxf.jaxrs.impl.MetadataMap;
+import org.apache.cxf.jaxrs.impl.ResponseImpl;
+import org.apache.cxf.jaxrs.utils.JAXRSUtils;
+import org.apache.cxf.message.ExchangeImpl;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.message.MessageImpl;
+import org.apache.cxf.phase.AbstractPhaseInterceptor;
+import org.apache.cxf.phase.Phase;
+import org.apache.cxf.phase.PhaseInterceptorChain;
+
+/**
+ * Builds the real server and client contexts the JOSE filters see at runtime.
+ */
+final class JoseFilterTestSupport {
+ interface Action {
+ void run() throws Exception;
+ }
+
+ private JoseFilterTestSupport() {
+ }
+
+ static Message serverRequest(String httpMethod, byte[] body) {
+ return serverRequest(httpMethod, body, Integer.toString(body.length));
+ }
+
+ static Message serverRequest(String httpMethod, byte[] body, String
contentLength) {
+ Message message = new MessageImpl();
+ message.setExchange(new ExchangeImpl());
+ message.put(Message.HTTP_REQUEST_METHOD, httpMethod);
+ message.setContent(InputStream.class, new TransportInputStream(body));
+ message.put(Message.PROTOCOL_HEADERS, headers(contentLength));
+ return message;
+ }
+
+ /**
+ * A request with no entity stream at all, as the local transport sends a
GET.
+ */
+ static Message serverRequestWithoutStream(String httpMethod) {
+ Message message = new MessageImpl();
+ message.setExchange(new ExchangeImpl());
+ message.put(Message.HTTP_REQUEST_METHOD, httpMethod);
+ message.put(Message.PROTOCOL_HEADERS, new MetadataMap<String,
String>());
+ return message;
+ }
+
+ static ClientRequestContext clientRequest(String httpMethod) {
+ Message message = new MessageImpl();
+ message.setExchange(new ExchangeImpl());
+ message.put(Message.HTTP_REQUEST_METHOD, httpMethod);
+ message.put(Message.PROTOCOL_HEADERS, new MetadataMap<String,
String>());
+ return new ClientRequestContextImpl(message, true);
+ }
+
+ /**
+ * A 200 response whose entity is the raw response stream, as
ClientResponseFilterInterceptor builds it.
+ */
+ static ClientResponseContext clientResponse(byte[] body) {
+ return clientResponse(body, Integer.toString(body.length));
+ }
+
+ static ClientResponseContext clientResponse(byte[] body, String
contentLength) {
+ Message message = new MessageImpl();
+ message.setExchange(new ExchangeImpl());
+ message.put(Message.RESPONSE_CODE, 200);
+ message.setContent(InputStream.class, new TransportInputStream(body));
+ message.put(Message.PROTOCOL_HEADERS, headers(contentLength));
+ ResponseImpl response = (ResponseImpl)JAXRSUtils.toResponseBuilder(200)
+ .entity(message.getContent(InputStream.class)).build();
+ return new ClientResponseContextImpl(response, message);
+ }
+
+ /**
+ * A 200 response with no entity, so that getEntityStream() returns null.
+ */
+ static ClientResponseContext clientResponseWithoutEntity() {
+ Message message = new MessageImpl();
+ message.setExchange(new ExchangeImpl());
+ message.put(Message.RESPONSE_CODE, 200);
+ message.put(Message.PROTOCOL_HEADERS, new MetadataMap<String,
String>());
+ ResponseImpl response =
(ResponseImpl)JAXRSUtils.toResponseBuilder(200).build();
+ return new ClientResponseContextImpl(response, message);
+ }
+
+ /**
+ * Runs the action inside an interceptor chain so that the filter sees a
current message,
+ * rethrowing whatever the action threw.
+ */
+ static void runInPhase(Message message, Action action) throws Exception {
+ PhaseInterceptorChain chain = new PhaseInterceptorChain(new
PhaseManagerImpl().getInPhases());
+ chain.add(new AbstractPhaseInterceptor<Message>(Phase.INVOKE) {
+ @Override
+ public void handleMessage(Message m) throws Fault {
+ try {
+ action.run();
+ } catch (Exception ex) {
+ throw new Fault(ex);
+ }
+ }
+ });
+ message.setInterceptorChain(chain);
+ chain.doIntercept(message);
+ Exception ex = message.getContent(Exception.class);
+ if (ex instanceof Fault && ex.getCause() instanceof Exception) {
+ throw (Exception)ex.getCause();
+ } else if (ex != null) {
+ throw ex;
+ }
+ }
+
+ static void runInPhase(Action action) throws Exception {
+ Message message = new MessageImpl();
+ message.setExchange(new ExchangeImpl());
+ runInPhase(message, action);
+ }
+
+ private static MetadataMap<String, String> headers(String contentLength) {
+ MetadataMap<String, String> headers = new MetadataMap<>();
+ headers.putSingle(HttpHeaders.CONTENT_TYPE, "application/jose");
+ headers.putSingle(HttpHeaders.CONTENT_LENGTH, contentLength);
+ return headers;
+ }
+
+ /**
+ * Fails on use once closed, as a servlet or HTTP client input stream does.
+ */
+ private static final class TransportInputStream extends FilterInputStream {
+ private boolean closed;
+
+ TransportInputStream(byte[] body) {
+ super(new ByteArrayInputStream(body));
+ }
+
+ @Override
+ public int read() throws IOException {
+ checkOpen();
+ return super.read();
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ checkOpen();
+ return super.read(b, off, len);
+ }
+
+ @Override
+ public int available() throws IOException {
+ checkOpen();
+ return super.available();
+ }
+
+ @Override
+ public void close() throws IOException {
+ closed = true;
+ super.close();
+ }
+
+ private void checkOpen() throws IOException {
+ if (closed) {
+ throw new IOException("Stream closed");
+ }
+ }
+ }
+}
diff --git
a/rt/rs/security/jose-parent/jose-jaxrs/src/test/java/org/apache/cxf/rs/security/jose/jaxrs/JweClientResponseFilterTest.java
b/rt/rs/security/jose-parent/jose-jaxrs/src/test/java/org/apache/cxf/rs/security/jose/jaxrs/JweClientResponseFilterTest.java
new file mode 100644
index 00000000000..3a31202a8a6
--- /dev/null
+++
b/rt/rs/security/jose-parent/jose-jaxrs/src/test/java/org/apache/cxf/rs/security/jose/jaxrs/JweClientResponseFilterTest.java
@@ -0,0 +1,80 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.rs.security.jose.jaxrs;
+
+import java.nio.charset.StandardCharsets;
+
+import jakarta.ws.rs.client.ClientResponseContext;
+import org.apache.cxf.helpers.IOUtils;
+import org.apache.cxf.rs.security.jose.jwa.ContentAlgorithm;
+import org.apache.cxf.rs.security.jose.jwe.JweHeaders;
+import org.apache.cxf.rs.security.jose.jwe.JweUtils;
+
+import org.junit.Test;
+
+import static
org.apache.cxf.rs.security.jose.jaxrs.JoseFilterTestSupport.clientRequest;
+import static
org.apache.cxf.rs.security.jose.jaxrs.JoseFilterTestSupport.clientResponse;
+import static
org.apache.cxf.rs.security.jose.jaxrs.JoseFilterTestSupport.clientResponseWithoutEntity;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+public class JweClientResponseFilterTest {
+ private static final byte[] KEY =
"0123456789abcdef".getBytes(StandardCharsets.UTF_8);
+ private static final String PAYLOAD = "{\"id\":\"123\"}";
+
+ @Test
+ public void testDeleteResponseIsDecrypted() throws Exception {
+ ClientResponseContext res = clientResponse(encrypt(PAYLOAD));
+
+ JweClientResponseFilter filter = new JweClientResponseFilter();
+ filter.setDecryptionProvider(JweUtils.getDirectKeyJweDecryption(KEY,
ContentAlgorithm.A128GCM));
+ filter.filter(clientRequest("DELETE"), res);
+
+ assertEquals(PAYLOAD,
IOUtils.readStringFromStream(res.getEntityStream()));
+ }
+
+ @Test
+ public void testEmptyDeleteResponseIsAccepted() throws Exception {
+ ClientResponseContext res = clientResponse(new byte[0]);
+
+ JweClientResponseFilter filter = new JweClientResponseFilter();
+ filter.setDecryptionProvider(JweUtils.getDirectKeyJweDecryption(KEY,
ContentAlgorithm.A128GCM));
+ filter.filter(clientRequest("DELETE"), res);
+
+ assertEquals("", IOUtils.readStringFromStream(res.getEntityStream()));
+ }
+
+ @Test
+ public void testResponseWithoutEntityIsAccepted() throws Exception {
+ ClientResponseContext res = clientResponseWithoutEntity();
+
+ JweClientResponseFilter filter = new JweClientResponseFilter();
+ filter.setDecryptionProvider(JweUtils.getDirectKeyJweDecryption(KEY,
ContentAlgorithm.A128GCM));
+ filter.filter(clientRequest("GET"), res);
+
+ assertNull(res.getEntityStream());
+ }
+
+ private static byte[] encrypt(String payload) {
+ JweHeaders headers = new JweHeaders(ContentAlgorithm.A128GCM);
+ headers.setContentType("application/json");
+ return JweUtils.getDirectKeyJweEncryption(KEY,
ContentAlgorithm.A128GCM)
+ .encrypt(payload.getBytes(StandardCharsets.UTF_8),
headers).getBytes(StandardCharsets.UTF_8);
+ }
+}
diff --git
a/rt/rs/security/jose-parent/jose-jaxrs/src/test/java/org/apache/cxf/rs/security/jose/jaxrs/JweContainerRequestFilterTest.java
b/rt/rs/security/jose-parent/jose-jaxrs/src/test/java/org/apache/cxf/rs/security/jose/jaxrs/JweContainerRequestFilterTest.java
new file mode 100644
index 00000000000..eb67625660e
--- /dev/null
+++
b/rt/rs/security/jose-parent/jose-jaxrs/src/test/java/org/apache/cxf/rs/security/jose/jaxrs/JweContainerRequestFilterTest.java
@@ -0,0 +1,81 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.rs.security.jose.jaxrs;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.cxf.helpers.IOUtils;
+import org.apache.cxf.jaxrs.impl.ContainerRequestContextImpl;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.rs.security.jose.jwa.ContentAlgorithm;
+import org.apache.cxf.rs.security.jose.jwe.JweHeaders;
+import org.apache.cxf.rs.security.jose.jwe.JweUtils;
+
+import org.junit.Test;
+
+import static
org.apache.cxf.rs.security.jose.jaxrs.JoseFilterTestSupport.serverRequest;
+import static
org.apache.cxf.rs.security.jose.jaxrs.JoseFilterTestSupport.serverRequestWithoutStream;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+public class JweContainerRequestFilterTest {
+ private static final byte[] KEY =
"0123456789abcdef".getBytes(StandardCharsets.UTF_8);
+ private static final String PAYLOAD = "{\"id\":\"123\"}";
+
+ @Test
+ public void testDeleteBodyIsDecrypted() throws Exception {
+ Message message = serverRequest("DELETE", encrypt(PAYLOAD));
+
+ JweContainerRequestFilter filter = new JweContainerRequestFilter();
+ filter.setDecryptionProvider(JweUtils.getDirectKeyJweDecryption(KEY,
ContentAlgorithm.A128GCM));
+ filter.filter(new ContainerRequestContextImpl(message, true, false));
+
+ assertEquals(PAYLOAD,
IOUtils.readStringFromStream(message.getContent(InputStream.class)));
+ }
+
+ @Test
+ public void testGetWithoutBodyIsAccepted() throws Exception {
+ Message message = serverRequest("GET", new byte[0]);
+
+ JweContainerRequestFilter filter = new JweContainerRequestFilter();
+ filter.setDecryptionProvider(JweUtils.getDirectKeyJweDecryption(KEY,
ContentAlgorithm.A128GCM));
+ filter.filter(new ContainerRequestContextImpl(message, true, false));
+
+ assertEquals("",
IOUtils.readStringFromStream(message.getContent(InputStream.class)));
+ }
+
+ @Test
+ public void testGetWithoutEntityStreamIsAccepted() throws Exception {
+ Message message = serverRequestWithoutStream("GET");
+
+ JweContainerRequestFilter filter = new JweContainerRequestFilter();
+ filter.setDecryptionProvider(JweUtils.getDirectKeyJweDecryption(KEY,
ContentAlgorithm.A128GCM));
+ filter.filter(new ContainerRequestContextImpl(message, true, false));
+
+ assertNull(message.getContent(InputStream.class));
+ }
+
+ private static byte[] encrypt(String payload) {
+ JweHeaders headers = new JweHeaders(ContentAlgorithm.A128GCM);
+ headers.setContentType("application/json");
+ return JweUtils.getDirectKeyJweEncryption(KEY,
ContentAlgorithm.A128GCM)
+ .encrypt(payload.getBytes(StandardCharsets.UTF_8),
headers).getBytes(StandardCharsets.UTF_8);
+ }
+}
diff --git
a/rt/rs/security/jose-parent/jose-jaxrs/src/test/java/org/apache/cxf/rs/security/jose/jaxrs/JweJsonClientResponseFilterTest.java
b/rt/rs/security/jose-parent/jose-jaxrs/src/test/java/org/apache/cxf/rs/security/jose/jaxrs/JweJsonClientResponseFilterTest.java
new file mode 100644
index 00000000000..9b2ead5e8a7
--- /dev/null
+++
b/rt/rs/security/jose-parent/jose-jaxrs/src/test/java/org/apache/cxf/rs/security/jose/jaxrs/JweJsonClientResponseFilterTest.java
@@ -0,0 +1,83 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.rs.security.jose.jaxrs;
+
+import java.nio.charset.StandardCharsets;
+
+import jakarta.ws.rs.client.ClientResponseContext;
+import org.apache.cxf.helpers.IOUtils;
+import org.apache.cxf.rs.security.jose.jwa.ContentAlgorithm;
+import org.apache.cxf.rs.security.jose.jwe.JweHeaders;
+import org.apache.cxf.rs.security.jose.jwe.JweJsonProducer;
+import org.apache.cxf.rs.security.jose.jwe.JweUtils;
+
+import org.junit.Test;
+
+import static
org.apache.cxf.rs.security.jose.jaxrs.JoseFilterTestSupport.clientRequest;
+import static
org.apache.cxf.rs.security.jose.jaxrs.JoseFilterTestSupport.clientResponse;
+import static
org.apache.cxf.rs.security.jose.jaxrs.JoseFilterTestSupport.clientResponseWithoutEntity;
+import static
org.apache.cxf.rs.security.jose.jaxrs.JoseFilterTestSupport.runInPhase;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+public class JweJsonClientResponseFilterTest {
+ private static final byte[] KEY =
"0123456789abcdef".getBytes(StandardCharsets.UTF_8);
+ private static final String PAYLOAD = "{\"id\":\"123\"}";
+
+ @Test
+ public void testDeleteResponseIsDecrypted() throws Exception {
+ ClientResponseContext res = clientResponse(encrypt(PAYLOAD));
+
+ JweJsonClientResponseFilter filter = new JweJsonClientResponseFilter();
+ filter.setDecryptionProvider(JweUtils.getDirectKeyJweDecryption(KEY,
ContentAlgorithm.A128GCM));
+ runInPhase(() -> filter.filter(clientRequest("DELETE"), res));
+
+ assertEquals(PAYLOAD,
IOUtils.readStringFromStream(res.getEntityStream()));
+ }
+
+ @Test
+ public void testEmptyDeleteResponseIsAccepted() throws Exception {
+ ClientResponseContext res = clientResponse(new byte[0]);
+
+ JweJsonClientResponseFilter filter = new JweJsonClientResponseFilter();
+ filter.setDecryptionProvider(JweUtils.getDirectKeyJweDecryption(KEY,
ContentAlgorithm.A128GCM));
+ runInPhase(() -> filter.filter(clientRequest("DELETE"), res));
+
+ assertEquals("", IOUtils.readStringFromStream(res.getEntityStream()));
+ }
+
+ @Test
+ public void testResponseWithoutEntityIsAccepted() throws Exception {
+ ClientResponseContext res = clientResponseWithoutEntity();
+
+ JweJsonClientResponseFilter filter = new JweJsonClientResponseFilter();
+ filter.setDecryptionProvider(JweUtils.getDirectKeyJweDecryption(KEY,
ContentAlgorithm.A128GCM));
+ runInPhase(() -> filter.filter(clientRequest("GET"), res));
+
+ assertNull(res.getEntityStream());
+ }
+
+ private static byte[] encrypt(String payload) {
+ JweHeaders headers = new JweHeaders(ContentAlgorithm.A128GCM);
+ headers.setContentType("application/json");
+ return new JweJsonProducer(headers,
payload.getBytes(StandardCharsets.UTF_8))
+ .encryptWith(JweUtils.getDirectKeyJweEncryption(KEY,
ContentAlgorithm.A128GCM))
+ .getBytes(StandardCharsets.UTF_8);
+ }
+}
diff --git
a/rt/rs/security/jose-parent/jose-jaxrs/src/test/java/org/apache/cxf/rs/security/jose/jaxrs/JweJsonContainerRequestFilterTest.java
b/rt/rs/security/jose-parent/jose-jaxrs/src/test/java/org/apache/cxf/rs/security/jose/jaxrs/JweJsonContainerRequestFilterTest.java
new file mode 100644
index 00000000000..39b1dd3e8a2
--- /dev/null
+++
b/rt/rs/security/jose-parent/jose-jaxrs/src/test/java/org/apache/cxf/rs/security/jose/jaxrs/JweJsonContainerRequestFilterTest.java
@@ -0,0 +1,86 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.rs.security.jose.jaxrs;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+import jakarta.ws.rs.core.Response;
+import org.apache.cxf.helpers.IOUtils;
+import org.apache.cxf.jaxrs.impl.ContainerRequestContextImpl;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.rs.security.jose.jwa.ContentAlgorithm;
+import org.apache.cxf.rs.security.jose.jwe.JweHeaders;
+import org.apache.cxf.rs.security.jose.jwe.JweJsonProducer;
+import org.apache.cxf.rs.security.jose.jwe.JweUtils;
+
+import org.junit.Test;
+
+import static
org.apache.cxf.rs.security.jose.jaxrs.JoseFilterTestSupport.runInPhase;
+import static
org.apache.cxf.rs.security.jose.jaxrs.JoseFilterTestSupport.serverRequest;
+import static
org.apache.cxf.rs.security.jose.jaxrs.JoseFilterTestSupport.serverRequestWithoutStream;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+public class JweJsonContainerRequestFilterTest {
+ private static final byte[] KEY =
"0123456789abcdef".getBytes(StandardCharsets.UTF_8);
+ private static final String PAYLOAD = "{\"id\":\"123\"}";
+
+ @Test
+ public void testDeleteBodyIsDecrypted() throws Exception {
+ Message message = serverRequest("DELETE", encrypt(PAYLOAD));
+
+ JweJsonContainerRequestFilter filter = new
JweJsonContainerRequestFilter();
+ filter.setDecryptionProvider(JweUtils.getDirectKeyJweDecryption(KEY,
ContentAlgorithm.A128GCM));
+ runInPhase(message, () -> filter.filter(new
ContainerRequestContextImpl(message, true, false)));
+
+ assertEquals(PAYLOAD,
IOUtils.readStringFromStream(message.getContent(InputStream.class)));
+ }
+
+ @Test
+ public void testGetWithoutBodyIsAccepted() throws Exception {
+ Message message = serverRequest("GET", new byte[0]);
+
+ JweJsonContainerRequestFilter filter = new
JweJsonContainerRequestFilter();
+ filter.setDecryptionProvider(JweUtils.getDirectKeyJweDecryption(KEY,
ContentAlgorithm.A128GCM));
+ runInPhase(message, () -> filter.filter(new
ContainerRequestContextImpl(message, true, false)));
+
+ assertNull(message.getExchange().get(Response.class));
+ assertEquals("",
IOUtils.readStringFromStream(message.getContent(InputStream.class)));
+ }
+
+ @Test
+ public void testGetWithoutEntityStreamIsAccepted() throws Exception {
+ Message message = serverRequestWithoutStream("GET");
+
+ JweJsonContainerRequestFilter filter = new
JweJsonContainerRequestFilter();
+ filter.setDecryptionProvider(JweUtils.getDirectKeyJweDecryption(KEY,
ContentAlgorithm.A128GCM));
+ runInPhase(message, () -> filter.filter(new
ContainerRequestContextImpl(message, true, false)));
+
+ assertNull(message.getExchange().get(Response.class));
+ }
+
+ private static byte[] encrypt(String payload) {
+ JweHeaders headers = new JweHeaders(ContentAlgorithm.A128GCM);
+ headers.setContentType("application/json");
+ return new JweJsonProducer(headers,
payload.getBytes(StandardCharsets.UTF_8))
+ .encryptWith(JweUtils.getDirectKeyJweEncryption(KEY,
ContentAlgorithm.A128GCM))
+ .getBytes(StandardCharsets.UTF_8);
+ }
+}
diff --git
a/rt/rs/security/jose-parent/jose-jaxrs/src/test/java/org/apache/cxf/rs/security/jose/jaxrs/JwsClientResponseFilterTest.java
b/rt/rs/security/jose-parent/jose-jaxrs/src/test/java/org/apache/cxf/rs/security/jose/jaxrs/JwsClientResponseFilterTest.java
new file mode 100644
index 00000000000..df14dd3ddb9
--- /dev/null
+++
b/rt/rs/security/jose-parent/jose-jaxrs/src/test/java/org/apache/cxf/rs/security/jose/jaxrs/JwsClientResponseFilterTest.java
@@ -0,0 +1,111 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.rs.security.jose.jaxrs;
+
+import java.nio.charset.StandardCharsets;
+
+import jakarta.ws.rs.client.ClientResponseContext;
+import org.apache.cxf.helpers.IOUtils;
+import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm;
+import org.apache.cxf.rs.security.jose.jws.HmacJwsSignatureProvider;
+import org.apache.cxf.rs.security.jose.jws.HmacJwsSignatureVerifier;
+import org.apache.cxf.rs.security.jose.jws.JwsCompactProducer;
+import org.apache.cxf.rs.security.jose.jws.JwsException;
+import org.apache.cxf.rs.security.jose.jws.JwsHeaders;
+
+import org.junit.Test;
+
+import static
org.apache.cxf.rs.security.jose.jaxrs.JoseFilterTestSupport.clientRequest;
+import static
org.apache.cxf.rs.security.jose.jaxrs.JoseFilterTestSupport.clientResponse;
+import static
org.apache.cxf.rs.security.jose.jaxrs.JoseFilterTestSupport.clientResponseWithoutEntity;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+public class JwsClientResponseFilterTest {
+ private static final String BAD_KEY =
"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75"
+ + "aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow";
+ private static final String GOOD_KEY =
"09Y_RK7l5rAY9QY7EblYQNuYbu9cy1j7ovCbkeIyAKN8LIeRL-3H8g"
+ + "c8kZSYzAQ1uTRC_egZ_8cgZSZa9T5nmQ";
+ private static final String PAYLOAD = "{\"id\":\"123\"}";
+
+ @Test
+ public void testDeleteResponseIsVerified() throws Exception {
+ ClientResponseContext res = clientResponse(sign(PAYLOAD, GOOD_KEY));
+ createFilter().filter(clientRequest("DELETE"), res);
+
+ assertEquals(PAYLOAD,
IOUtils.readStringFromStream(res.getEntityStream()));
+ }
+
+ @Test
+ public void testDeleteResponseSignedWithWrongKeyIsRejected() throws
Exception {
+ ClientResponseContext res = clientResponse(sign(PAYLOAD, BAD_KEY));
+ try {
+ createFilter().filter(clientRequest("DELETE"), res);
+ fail("Failure expected on a DELETE response signed with the wrong
key");
+ } catch (JwsException ex) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testEmptyDeleteResponseIsAccepted() throws Exception {
+ ClientResponseContext res = clientResponse(new byte[0]);
+ createFilter().filter(clientRequest("DELETE"), res);
+
+ assertEquals("", IOUtils.readStringFromStream(res.getEntityStream()));
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ public void testCheckEmptyStreamDoesNotTrustZeroContentLength() throws
Exception {
+ ClientResponseContext res = clientResponse(sign(PAYLOAD, BAD_KEY),
"0");
+ JwsClientResponseFilter filter = createFilter();
+ filter.setCheckEmptyStream(true);
+ try {
+ filter.filter(clientRequest("POST"), res);
+ fail("Failure expected on a body signed with the wrong key despite
Content-Length: 0");
+ } catch (JwsException ex) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testResponseWithoutEntityIsAccepted() throws Exception {
+ ClientResponseContext res = clientResponseWithoutEntity();
+ createFilter().filter(clientRequest("GET"), res);
+
+ assertNull(res.getEntityStream());
+ }
+
+ private static JwsClientResponseFilter createFilter() {
+ JwsClientResponseFilter filter = new JwsClientResponseFilter();
+ filter.setSignatureVerifier(new HmacJwsSignatureVerifier(GOOD_KEY,
SignatureAlgorithm.HS256));
+ filter.setValidateHttpHeaders(false);
+ return filter;
+ }
+
+ private static byte[] sign(String payload, String key) {
+ JwsHeaders headers = new JwsHeaders(SignatureAlgorithm.HS256);
+ headers.setContentType("application/json");
+ return new JwsCompactProducer(headers, payload)
+ .signWith(new HmacJwsSignatureProvider(key,
SignatureAlgorithm.HS256))
+ .getBytes(StandardCharsets.UTF_8);
+ }
+}
diff --git
a/rt/rs/security/jose-parent/jose-jaxrs/src/test/java/org/apache/cxf/rs/security/jose/jaxrs/JwsContainerRequestFilterTest.java
b/rt/rs/security/jose-parent/jose-jaxrs/src/test/java/org/apache/cxf/rs/security/jose/jaxrs/JwsContainerRequestFilterTest.java
new file mode 100644
index 00000000000..bcc2a4e4443
--- /dev/null
+++
b/rt/rs/security/jose-parent/jose-jaxrs/src/test/java/org/apache/cxf/rs/security/jose/jaxrs/JwsContainerRequestFilterTest.java
@@ -0,0 +1,127 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.rs.security.jose.jaxrs;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+import jakarta.ws.rs.core.Response;
+import org.apache.cxf.helpers.IOUtils;
+import org.apache.cxf.jaxrs.impl.ContainerRequestContextImpl;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm;
+import org.apache.cxf.rs.security.jose.jws.HmacJwsSignatureProvider;
+import org.apache.cxf.rs.security.jose.jws.HmacJwsSignatureVerifier;
+import org.apache.cxf.rs.security.jose.jws.JwsCompactProducer;
+import org.apache.cxf.rs.security.jose.jws.JwsHeaders;
+
+import org.junit.Test;
+
+import static
org.apache.cxf.rs.security.jose.jaxrs.JoseFilterTestSupport.runInPhase;
+import static
org.apache.cxf.rs.security.jose.jaxrs.JoseFilterTestSupport.serverRequest;
+import static
org.apache.cxf.rs.security.jose.jaxrs.JoseFilterTestSupport.serverRequestWithoutStream;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+public class JwsContainerRequestFilterTest {
+ private static final String BAD_KEY =
"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75"
+ + "aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow";
+ private static final String GOOD_KEY =
"09Y_RK7l5rAY9QY7EblYQNuYbu9cy1j7ovCbkeIyAKN8LIeRL-3H8g"
+ + "c8kZSYzAQ1uTRC_egZ_8cgZSZa9T5nmQ";
+ private static final String PAYLOAD = "{\"id\":\"123\"}";
+
+ @Test
+ public void testDeleteBodyIsVerified() throws Exception {
+ Message message = serverRequest("DELETE", sign(PAYLOAD, GOOD_KEY));
+ filter(message);
+
+ assertNull(message.getExchange().get(Response.class));
+ assertEquals(PAYLOAD,
IOUtils.readStringFromStream(message.getContent(InputStream.class)));
+ }
+
+ @Test
+ public void testDeleteBodySignedWithWrongKeyIsRejected() throws Exception {
+ Message message = serverRequest("DELETE", sign(PAYLOAD, BAD_KEY));
+ filter(message);
+
+ assertRejected(message);
+ }
+
+ @Test
+ public void testZeroContentLengthDoesNotSkipVerification() throws
Exception {
+ // the header claims there is no body but the stream has one: the
stream decides
+ Message message = serverRequest("DELETE", sign(PAYLOAD, BAD_KEY), "0");
+ filter(message);
+
+ assertRejected(message);
+ }
+
+ @Test
+ public void testGetWithoutBodyIsAccepted() throws Exception {
+ Message message = serverRequest("GET", new byte[0]);
+ filter(message);
+
+ assertNull(message.getExchange().get(Response.class));
+ assertEquals("",
IOUtils.readStringFromStream(message.getContent(InputStream.class)));
+ }
+
+ @Test
+ public void testCheckEmptyStreamDoesNotTrustZeroContentLength() throws
Exception {
+ Message message = serverRequest("POST", sign(PAYLOAD, BAD_KEY), "0");
+ filter(message, true);
+
+ assertRejected(message);
+ }
+
+ @Test
+ public void testGetWithoutEntityStreamIsAccepted() throws Exception {
+ Message message = serverRequestWithoutStream("GET");
+ filter(message);
+
+ assertNull(message.getExchange().get(Response.class));
+ }
+
+ private static void filter(Message message) throws Exception {
+ filter(message, false);
+ }
+
+ @SuppressWarnings("deprecation")
+ private static void filter(Message message, boolean checkEmptyStream)
throws Exception {
+ JwsContainerRequestFilter filter = new JwsContainerRequestFilter();
+ filter.setCheckEmptyStream(checkEmptyStream);
+ filter.setSignatureVerifier(new HmacJwsSignatureVerifier(GOOD_KEY,
SignatureAlgorithm.HS256));
+ filter.setValidateHttpHeaders(false);
+ runInPhase(message, () -> filter.filter(new
ContainerRequestContextImpl(message, true, false)));
+ }
+
+ private static void assertRejected(Message message) {
+ Response response = message.getExchange().get(Response.class);
+ assertNotNull(response);
+ assertEquals(400, response.getStatus());
+ }
+
+ private static byte[] sign(String payload, String key) {
+ JwsHeaders headers = new JwsHeaders(SignatureAlgorithm.HS256);
+ headers.setContentType("application/json");
+ return new JwsCompactProducer(headers, payload)
+ .signWith(new HmacJwsSignatureProvider(key,
SignatureAlgorithm.HS256))
+ .getBytes(StandardCharsets.UTF_8);
+ }
+}
diff --git
a/rt/rs/security/jose-parent/jose-jaxrs/src/test/java/org/apache/cxf/rs/security/jose/jaxrs/JwsJsonClientResponseFilterTest.java
b/rt/rs/security/jose-parent/jose-jaxrs/src/test/java/org/apache/cxf/rs/security/jose/jaxrs/JwsJsonClientResponseFilterTest.java
new file mode 100644
index 00000000000..a199bf89844
--- /dev/null
+++
b/rt/rs/security/jose-parent/jose-jaxrs/src/test/java/org/apache/cxf/rs/security/jose/jaxrs/JwsJsonClientResponseFilterTest.java
@@ -0,0 +1,97 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.rs.security.jose.jaxrs;
+
+import java.nio.charset.StandardCharsets;
+
+import jakarta.ws.rs.client.ClientResponseContext;
+import org.apache.cxf.helpers.IOUtils;
+import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm;
+import org.apache.cxf.rs.security.jose.jws.HmacJwsSignatureProvider;
+import org.apache.cxf.rs.security.jose.jws.HmacJwsSignatureVerifier;
+import org.apache.cxf.rs.security.jose.jws.JwsException;
+import org.apache.cxf.rs.security.jose.jws.JwsHeaders;
+import org.apache.cxf.rs.security.jose.jws.JwsJsonProducer;
+
+import org.junit.Test;
+
+import static
org.apache.cxf.rs.security.jose.jaxrs.JoseFilterTestSupport.clientRequest;
+import static
org.apache.cxf.rs.security.jose.jaxrs.JoseFilterTestSupport.clientResponse;
+import static
org.apache.cxf.rs.security.jose.jaxrs.JoseFilterTestSupport.clientResponseWithoutEntity;
+import static
org.apache.cxf.rs.security.jose.jaxrs.JoseFilterTestSupport.runInPhase;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+public class JwsJsonClientResponseFilterTest {
+ private static final String BAD_KEY =
"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75"
+ + "aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow";
+ private static final String GOOD_KEY =
"09Y_RK7l5rAY9QY7EblYQNuYbu9cy1j7ovCbkeIyAKN8LIeRL-3H8g"
+ + "c8kZSYzAQ1uTRC_egZ_8cgZSZa9T5nmQ";
+ private static final String PAYLOAD = "{\"id\":\"123\"}";
+
+ @Test
+ public void testDeleteResponseIsVerified() throws Exception {
+ ClientResponseContext res = clientResponse(sign(PAYLOAD, GOOD_KEY));
+ runInPhase(() -> createFilter().filter(clientRequest("DELETE"), res));
+
+ assertEquals(PAYLOAD,
IOUtils.readStringFromStream(res.getEntityStream()));
+ }
+
+ @Test
+ public void testDeleteResponseSignedWithWrongKeyIsRejected() throws
Exception {
+ ClientResponseContext res = clientResponse(sign(PAYLOAD, BAD_KEY));
+ try {
+ runInPhase(() -> createFilter().filter(clientRequest("DELETE"),
res));
+ fail("Failure expected on a DELETE response signed with the wrong
key");
+ } catch (JwsException ex) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testEmptyDeleteResponseIsAccepted() throws Exception {
+ ClientResponseContext res = clientResponse(new byte[0]);
+ runInPhase(() -> createFilter().filter(clientRequest("DELETE"), res));
+
+ assertEquals("", IOUtils.readStringFromStream(res.getEntityStream()));
+ }
+
+ @Test
+ public void testResponseWithoutEntityIsAccepted() throws Exception {
+ ClientResponseContext res = clientResponseWithoutEntity();
+ runInPhase(() -> createFilter().filter(clientRequest("GET"), res));
+
+ assertNull(res.getEntityStream());
+ }
+
+ private static JwsJsonClientResponseFilter createFilter() {
+ JwsJsonClientResponseFilter filter = new JwsJsonClientResponseFilter();
+ filter.setSignatureVerifier(new HmacJwsSignatureVerifier(GOOD_KEY,
SignatureAlgorithm.HS256));
+ return filter;
+ }
+
+ private static byte[] sign(String payload, String key) {
+ JwsHeaders headers = new JwsHeaders(SignatureAlgorithm.HS256);
+ headers.setContentType("application/json");
+ JwsJsonProducer producer = new JwsJsonProducer(payload);
+ producer.signWith(new HmacJwsSignatureProvider(key,
SignatureAlgorithm.HS256), headers);
+ return
producer.getJwsJsonSignedDocument().getBytes(StandardCharsets.UTF_8);
+ }
+}
diff --git
a/rt/rs/security/jose-parent/jose-jaxrs/src/test/java/org/apache/cxf/rs/security/jose/jaxrs/JwsJsonContainerRequestFilterTest.java
b/rt/rs/security/jose-parent/jose-jaxrs/src/test/java/org/apache/cxf/rs/security/jose/jaxrs/JwsJsonContainerRequestFilterTest.java
index f51e0fc397d..b8238ca00e0 100644
---
a/rt/rs/security/jose-parent/jose-jaxrs/src/test/java/org/apache/cxf/rs/security/jose/jaxrs/JwsJsonContainerRequestFilterTest.java
+++
b/rt/rs/security/jose-parent/jose-jaxrs/src/test/java/org/apache/cxf/rs/security/jose/jaxrs/JwsJsonContainerRequestFilterTest.java
@@ -46,6 +46,7 @@ import org.apache.cxf.rs.security.jose.jws.JwsJsonProducer;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
public class JwsJsonContainerRequestFilterTest {
@@ -97,6 +98,79 @@ public class JwsJsonContainerRequestFilterTest {
IOUtils.readStringFromStream(message.getContent(java.io.InputStream.class)));
}
+ @Test
+ public void testDeleteBodySignedWithWrongKeyIsRejected() throws Exception {
+ JwsJsonProducer producer = new JwsJsonProducer("{\"id\":\"123\"}");
+ producer.signWith(new HmacJwsSignatureProvider(BAD_KEY,
SignatureAlgorithm.HS256),
+ createHeaders("application/json"));
+ byte[] signedDocument =
producer.getJwsJsonSignedDocument().getBytes(StandardCharsets.UTF_8);
+
+ Message message = new MessageImpl();
+ message.setExchange(new ExchangeImpl());
+ message.put(Message.HTTP_REQUEST_METHOD, "DELETE");
+ message.setContent(java.io.InputStream.class, new
ByteArrayInputStream(signedDocument));
+
+ MetadataMap<String, String> headers = new MetadataMap<>();
+ headers.putSingle(HttpHeaders.CONTENT_TYPE, "application/json");
+ headers.putSingle(HttpHeaders.CONTENT_LENGTH,
Integer.toString(signedDocument.length));
+ message.put(Message.PROTOCOL_HEADERS, headers);
+
+ JwsJsonContainerRequestFilter filter = new
JwsJsonContainerRequestFilter();
+ filter.setSignatureVerifier(new HmacJwsSignatureVerifier(GOOD_KEY,
SignatureAlgorithm.HS256));
+ filter.setValidateHttpHeaders(false);
+
+ runInPhase(message, () -> {
+ try {
+ filter.filter(new ContainerRequestContextImpl(message, true,
false));
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ });
+
+ Response response = message.getExchange().get(Response.class);
+ assertNotNull(response);
+ assertEquals(400, response.getStatus());
+ }
+
+ @Test
+ public void testGetWithoutBodyIsAccepted() throws Exception {
+ Message message = JoseFilterTestSupport.serverRequest("GET", new
byte[0]);
+
+ JwsJsonContainerRequestFilter filter = new
JwsJsonContainerRequestFilter();
+ filter.setSignatureVerifier(new HmacJwsSignatureVerifier(GOOD_KEY,
SignatureAlgorithm.HS256));
+
+ runInPhase(message, () -> {
+ try {
+ filter.filter(new ContainerRequestContextImpl(message, true,
false));
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ });
+
+ assertNull(message.getContent(Exception.class));
+ assertNull(message.getExchange().get(Response.class));
+ assertEquals("",
IOUtils.readStringFromStream(message.getContent(java.io.InputStream.class)));
+ }
+
+ @Test
+ public void testGetWithoutEntityStreamIsAccepted() throws Exception {
+ Message message =
JoseFilterTestSupport.serverRequestWithoutStream("GET");
+
+ JwsJsonContainerRequestFilter filter = new
JwsJsonContainerRequestFilter();
+ filter.setSignatureVerifier(new HmacJwsSignatureVerifier(GOOD_KEY,
SignatureAlgorithm.HS256));
+
+ runInPhase(message, () -> {
+ try {
+ filter.filter(new ContainerRequestContextImpl(message, true,
false));
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ });
+
+ assertNull(message.getContent(Exception.class));
+ assertNull(message.getExchange().get(Response.class));
+ }
+
private String createSignedDocument(String payload) {
JwsJsonProducer producer = new JwsJsonProducer(payload);
producer.signWith(new HmacJwsSignatureProvider(BAD_KEY,
SignatureAlgorithm.HS256),