You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by co...@apache.org on 2021/05/27 11:34:50 UTC
[cxf] branch master updated: [CXF-8535] Query missing from
signature request-target (#795)
This is an automated email from the ASF dual-hosted git repository.
coheigea pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cxf.git
The following commit(s) were added to refs/heads/master by this push:
new 504cb84 [CXF-8535] Query missing from signature request-target (#795)
504cb84 is described below
commit 504cb84e0bf6043d3b787def4070c0649dd51c32
Author: eberntsen <ei...@fimbulvinter.com>
AuthorDate: Thu May 27 13:34:36 2021 +0200
[CXF-8535] Query missing from signature request-target (#795)
* [CXF-8535] Include query in request-target
* Fix codestyle issues
---
parent/pom.xml | 1 +
rt/rs/security/http-signature/pom.xml | 20 ++
.../filters/CreateSignatureInterceptor.java | 14 +-
.../filters/VerifySignatureFilter.java | 6 +-
.../httpsignature/utils/SignatureHeaderUtils.java | 12 +-
.../security/httpsignature/SpecExamplesTest.java | 214 +++++++++++++++++++--
6 files changed, 242 insertions(+), 25 deletions(-)
diff --git a/parent/pom.xml b/parent/pom.xml
index 835c826..78cf560 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -181,6 +181,7 @@
<cxf.persistence-api.version>2.2.3</cxf.persistence-api.version>
<cxf.plexus-archiver.version>4.2.0</cxf.plexus-archiver.version>
<cxf.plexus-utils.version>3.2.0</cxf.plexus-utils.version>
+ <cxf.powermock.version>2.0.4</cxf.powermock.version>
<cxf.reactivestreams.version>1.0.3</cxf.reactivestreams.version>
<cxf.reactor.version>3.4.6</cxf.reactor.version>
<cxf.rhino.version>1.7.13</cxf.rhino.version>
diff --git a/rt/rs/security/http-signature/pom.xml b/rt/rs/security/http-signature/pom.xml
index 1c4c3b8..a8d00de 100644
--- a/rt/rs/security/http-signature/pom.xml
+++ b/rt/rs/security/http-signature/pom.xml
@@ -70,6 +70,26 @@
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxrs</artifactId>
</dependency>
+
+ <dependency>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-module-junit4</artifactId>
+ <version>${cxf.powermock.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-api-mockito2</artifactId>
+ <version>${cxf.powermock.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>${cxf.mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+
</dependencies>
</project>
diff --git a/rt/rs/security/http-signature/src/main/java/org/apache/cxf/rs/security/httpsignature/filters/CreateSignatureInterceptor.java b/rt/rs/security/http-signature/src/main/java/org/apache/cxf/rs/security/httpsignature/filters/CreateSignatureInterceptor.java
index ca128f4..103cd72 100644
--- a/rt/rs/security/http-signature/src/main/java/org/apache/cxf/rs/security/httpsignature/filters/CreateSignatureInterceptor.java
+++ b/rt/rs/security/http-signature/src/main/java/org/apache/cxf/rs/security/httpsignature/filters/CreateSignatureInterceptor.java
@@ -18,10 +18,8 @@
*/
package org.apache.cxf.rs.security.httpsignature.filters;
-import java.io.IOException;
-import java.io.OutputStream;
+import java.io.*;
import java.nio.charset.StandardCharsets;
-
import javax.annotation.Priority;
import javax.ws.rs.Priorities;
import javax.ws.rs.client.ClientRequestContext;
@@ -35,7 +33,6 @@ import javax.ws.rs.core.UriInfo;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.WriterInterceptor;
import javax.ws.rs.ext.WriterInterceptorContext;
-
import org.apache.cxf.helpers.IOUtils;
import org.apache.cxf.io.CachedOutputStream;
import org.apache.cxf.jaxrs.utils.HttpUtils;
@@ -85,9 +82,8 @@ public class CreateSignatureInterceptor extends AbstractSignatureOutFilter
// Only sign the request if we have no Body.
if (requestContext.getEntity() == null) {
String method = requestContext.getMethod();
- String path = requestContext.getUri().getPath();
-
- performSignature(requestContext.getHeaders(), path, method);
+ performSignature(requestContext.getHeaders(),
+ SignatureHeaderUtils.createRequestTarget(requestContext.getUri()), method);
}
}
@@ -108,8 +104,8 @@ public class CreateSignatureInterceptor extends AbstractSignatureOutFilter
// We don't pass the HTTP method + URI for the response case
if (MessageUtils.isRequestor(m)) {
method = HttpUtils.getProtocolHeader(JAXRSUtils.getCurrentMessage(),
- Message.HTTP_REQUEST_METHOD, "");
- path = uriInfo.getRequestUri().getPath();
+ Message.HTTP_REQUEST_METHOD, "");
+ path = SignatureHeaderUtils.createRequestTarget(uriInfo.getRequestUri());
}
performSignature(writerInterceptorContext.getHeaders(), path, method);
diff --git a/rt/rs/security/http-signature/src/main/java/org/apache/cxf/rs/security/httpsignature/filters/VerifySignatureFilter.java b/rt/rs/security/http-signature/src/main/java/org/apache/cxf/rs/security/httpsignature/filters/VerifySignatureFilter.java
index 5e77e1c..444d019 100644
--- a/rt/rs/security/http-signature/src/main/java/org/apache/cxf/rs/security/httpsignature/filters/VerifySignatureFilter.java
+++ b/rt/rs/security/http-signature/src/main/java/org/apache/cxf/rs/security/httpsignature/filters/VerifySignatureFilter.java
@@ -18,14 +18,14 @@
*/
package org.apache.cxf.rs.security.httpsignature.filters;
-import java.io.ByteArrayInputStream;
-
+import java.io.*;
import javax.annotation.Priority;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.Priorities;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.ext.Provider;
+import org.apache.cxf.rs.security.httpsignature.utils.SignatureHeaderUtils;
/**
* RS CXF container Filter which verifies the Digest header, and then extracts signature data from the context
@@ -43,7 +43,7 @@ public class VerifySignatureFilter extends AbstractSignatureInFilter implements
}
verifySignature(requestCtx.getHeaders(),
- requestCtx.getUriInfo().getAbsolutePath().getPath(),
+ SignatureHeaderUtils.createRequestTarget(requestCtx.getUriInfo().getAbsolutePath()),
requestCtx.getMethod());
}
diff --git a/rt/rs/security/http-signature/src/main/java/org/apache/cxf/rs/security/httpsignature/utils/SignatureHeaderUtils.java b/rt/rs/security/http-signature/src/main/java/org/apache/cxf/rs/security/httpsignature/utils/SignatureHeaderUtils.java
index 7c14cd8..4bcc440 100644
--- a/rt/rs/security/http-signature/src/main/java/org/apache/cxf/rs/security/httpsignature/utils/SignatureHeaderUtils.java
+++ b/rt/rs/security/http-signature/src/main/java/org/apache/cxf/rs/security/httpsignature/utils/SignatureHeaderUtils.java
@@ -18,6 +18,7 @@
*/
package org.apache.cxf.rs.security.httpsignature.utils;
+import java.net.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Clock;
@@ -26,7 +27,6 @@ import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Stream;
-
import org.apache.cxf.rs.security.httpsignature.exception.DigestFailureException;
public final class SignatureHeaderUtils {
@@ -101,4 +101,14 @@ public final class SignatureHeaderUtils {
});
}
+ public static String createRequestTarget(URI uri) {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append(uri.getPath());
+
+ if (uri.getRawQuery() != null) {
+ stringBuilder.append("?");
+ stringBuilder.append(uri.getRawQuery());
+ }
+ return stringBuilder.toString();
+ }
}
diff --git a/rt/rs/security/http-signature/src/test/java/org/apache/cxf/rs/security/httpsignature/SpecExamplesTest.java b/rt/rs/security/http-signature/src/test/java/org/apache/cxf/rs/security/httpsignature/SpecExamplesTest.java
index 6d164c2..fc5a445 100644
--- a/rt/rs/security/http-signature/src/test/java/org/apache/cxf/rs/security/httpsignature/SpecExamplesTest.java
+++ b/rt/rs/security/http-signature/src/test/java/org/apache/cxf/rs/security/httpsignature/SpecExamplesTest.java
@@ -18,8 +18,9 @@
*/
package org.apache.cxf.rs.security.httpsignature;
-import java.io.File;
-import java.io.IOException;
+import java.io.*;
+import java.net.*;
+import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -30,25 +31,35 @@ import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
+import java.util.*;
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.core.MultivaluedHashMap;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.UriInfo;
+import org.apache.cxf.message.Message;
import org.apache.cxf.message.MessageImpl;
+import org.apache.cxf.phase.PhaseInterceptorChain;
+import org.apache.cxf.rs.security.httpsignature.filters.CreateSignatureInterceptor;
+import org.apache.cxf.rs.security.httpsignature.filters.VerifySignatureFilter;
import org.apache.cxf.rs.security.httpsignature.provider.KeyProvider;
import org.apache.cxf.rs.security.httpsignature.provider.MockAlgorithmProvider;
import org.apache.cxf.rs.security.httpsignature.provider.MockSecurityProvider;
-
import org.junit.BeforeClass;
import org.junit.Test;
-
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
import static org.junit.Assert.assertEquals;
+import static org.powermock.api.mockito.PowerMockito.mock;
+import static org.powermock.api.mockito.PowerMockito.mockStatic;
+import static org.powermock.api.mockito.PowerMockito.when;
/**
* Some examples from the Appendix C of the spec.
*/
+@PrepareForTest(PhaseInterceptorChain.class)
+@RunWith(PowerMockRunner.class)
public class SpecExamplesTest {
private static KeyProvider keyProvider;
@@ -56,7 +67,6 @@ public class SpecExamplesTest {
@BeforeClass
public static void setUp() throws IOException, InvalidKeySpecException {
-
try {
// Load keys
String basedir = System.getProperty("basedir");
@@ -108,11 +118,61 @@ public class SpecExamplesTest {
}
@Test
+ public void interceptorDefaultTest() {
+ URI uri = URI.create("https://www.example.com/foo?param=value&pet=dog");
+ String method = "POST";
+ MessageSigner messageSigner = new MessageSigner(keyProvider, "Test", Collections.singletonList("Date"));
+
+ CreateSignatureInterceptor interceptor = new CreateSignatureInterceptor();
+ interceptor.setMessageSigner(messageSigner);
+
+ Map<String, List<String>> headers = createMockHeaders();
+ MultivaluedMap<String, Object> requestHeaders = new MultivaluedHashMap<>();
+ MultivaluedMap<String, String> requestStringHeaders = new MultivaluedHashMap<>();
+ headers.forEach((header, value) -> {
+ requestHeaders.add(header, value.get(0));
+ requestStringHeaders.add(header, value.get(0));
+ });
+
+ mockStatic(PhaseInterceptorChain.class);
+ Message message = mock(Message.class);
+ when(PhaseInterceptorChain.getCurrentMessage()).thenReturn(message);
+
+ ClientRequestContext requestContext = getClientRequestContextMock(uri, method, requestHeaders);
+
+ interceptor.filter(requestContext);
+
+ String signatureHeader = (String) requestHeaders.get("Signature").get(0);
+ requestStringHeaders.add("Signature", signatureHeader);
+
+ String expectedHeader = "keyId=\"Test\",algorithm=\"rsa-sha256\","
+ + "signature=\"SjWJWbWN7i0wzBvtPl8rbASWz5xQW6mcJmn+ibttBqtifLN7Sazz"
+ + "6m79cNfwwb8DMJ5cou1s7uEGKKCs+FLEEaDV5lp7q25WqS+lavg7T8hc0GppauB"
+ + "6hbgEKTwblDHYGEtbGmtdHgVCk9SuS13F0hZ8FD0k/5OxEPXe5WozsbM=\"";
+
+ assertEquals(signatureHeader.replaceAll("headers=\"date\",", ""), expectedHeader);
+
+ // Verify that the request signature can be verified by the filter
+ MessageVerifier messageVerifier = new MessageVerifier(keyId -> publicKey);
+ messageVerifier.setAddDefaultRequiredHeaders(false);
+ messageVerifier.setSecurityProvider(new MockSecurityProvider());
+ messageVerifier.setAlgorithmProvider(new MockAlgorithmProvider());
+
+ VerifySignatureFilter verifySignatureFilter = new VerifySignatureFilter();
+ verifySignatureFilter.setMessageVerifier(messageVerifier);
+
+ ContainerRequestContext containerRequestContext =
+ getContainerRequestContextMock(uri, method, requestStringHeaders);
+
+ verifySignatureFilter.filter(containerRequestContext);
+ }
+
+ @Test
public void basicTest() throws IOException {
Map<String, List<String>> headers = createMockHeaders();
MessageSigner messageSigner = new MessageSigner(keyProvider, "Test",
- Arrays.asList("(request-target)", "host", "Date"));
+ Arrays.asList("(request-target)", "host", "Date"));
messageSigner.sign(headers, "/foo?param=value&pet=dog", "POST");
String signatureHeader = headers.get("Signature").get(0);
@@ -132,6 +192,58 @@ public class SpecExamplesTest {
}
@Test
+ public void interceptorBasicTest() {
+ URI uri = URI.create("https://www.example.com/foo?param=value&pet=dog");
+ String method = "POST";
+ MessageSigner messageSigner = new MessageSigner(keyProvider, "Test",
+ Arrays.asList("(request-target)", "host", "Date"));
+
+ CreateSignatureInterceptor interceptor = new CreateSignatureInterceptor();
+ interceptor.setMessageSigner(messageSigner);
+
+ Map<String, List<String>> headers = createMockHeaders();
+ MultivaluedMap<String, Object> requestHeaders = new MultivaluedHashMap<>();
+ MultivaluedMap<String, String> requestStringHeaders = new MultivaluedHashMap<>();
+ headers.forEach((header, value) -> {
+ requestHeaders.add(header, value.get(0));
+ requestStringHeaders.add(header, value.get(0));
+ });
+
+ mockStatic(PhaseInterceptorChain.class);
+ Message message = mock(Message.class);
+ when(PhaseInterceptorChain.getCurrentMessage()).thenReturn(message);
+
+ ClientRequestContext requestContext = getClientRequestContextMock(uri, method, requestHeaders);
+
+ interceptor.filter(requestContext);
+
+ String signatureHeader = (String) requestHeaders.get("Signature").get(0);
+ requestStringHeaders.add("Signature", signatureHeader);
+
+ String expectedHeader = "keyId=\"Test\",algorithm=\"rsa-sha256\","
+ + "headers=\"(request-target) host date\",signature=\"qdx+H7PHHDZgy4"
+ + "y/Ahn9Tny9V3GP6YgBPyUXMmoxWtLbHpUnXS2mg2+SbrQDMCJypxBLSPQR2aAjn"
+ + "7ndmw2iicw3HMbe8VfEdKFYRqzic+efkb3nndiv/x1xSHDJWeSWkx3ButlYSuBs"
+ + "kLu6kd9Fswtemr3lgdDEmn04swr2Os0=\"";
+
+ assertEquals(signatureHeader, expectedHeader);
+
+ // Verify that the request signature can be verified by the filter
+ MessageVerifier messageVerifier = new MessageVerifier(keyId -> publicKey);
+ messageVerifier.setAddDefaultRequiredHeaders(false);
+ messageVerifier.setSecurityProvider(new MockSecurityProvider());
+ messageVerifier.setAlgorithmProvider(new MockAlgorithmProvider());
+
+ VerifySignatureFilter verifySignatureFilter = new VerifySignatureFilter();
+ verifySignatureFilter.setMessageVerifier(messageVerifier);
+
+ ContainerRequestContext containerRequestContext =
+ getContainerRequestContextMock(uri, method, requestStringHeaders);
+
+ verifySignatureFilter.filter(containerRequestContext);
+ }
+
+ @Test
public void allHeadersTest() throws IOException {
Map<String, List<String>> headers = createMockHeaders();
@@ -155,6 +267,84 @@ public class SpecExamplesTest {
messageVerifier.verifyMessage(headers, "POST", "/foo?param=value&pet=dog", new MessageImpl());
}
+ @Test
+ public void interceptorAllHeadersTest() {
+ URI uri = URI.create("https://www.example.com/foo?param=value&pet=dog");
+ String method = "POST";
+ MessageSigner messageSigner = new MessageSigner(keyProvider, "Test",
+ Arrays.asList("(request-target)", "host", "date",
+ "content-type", "digest", "content-length"));
+
+ CreateSignatureInterceptor interceptor = new CreateSignatureInterceptor();
+
+ interceptor.setMessageSigner(messageSigner);
+
+ Map<String, List<String>> headers = createMockHeaders();
+ MultivaluedMap<String, Object> requestHeaders = new MultivaluedHashMap<>();
+ MultivaluedMap<String, String> requestStringHeaders = new MultivaluedHashMap<>();
+ headers.forEach((header, value) -> {
+ requestHeaders.add(header, value.get(0));
+ requestStringHeaders.add(header, value.get(0));
+ });
+
+ ClientRequestContext requestContext = getClientRequestContextMock(uri, method, requestHeaders);
+
+ mockStatic(PhaseInterceptorChain.class);
+ Message message = mock(Message.class);
+ when(PhaseInterceptorChain.getCurrentMessage()).thenReturn(message);
+
+ interceptor.filter(requestContext);
+
+ String signatureHeader = (String) requestHeaders.get("Signature").get(0);
+ requestStringHeaders.add("Signature", signatureHeader);
+
+ String expectedHeader = "keyId=\"Test\",algorithm=\"rsa-sha256\","
+ + "headers=\"(request-target) host date content-type digest content-length\","
+ + "signature=\"vSdrb+dS3EceC9bcwHSo4MlyKS59iFIrhgYkz8+oVLEEzmYZZvRs"
+ + "8rgOp+63LEM3v+MFHB32NfpB2bEKBIvB1q52LaEUHFv120V01IL+TAD48XaERZF"
+ + "ukWgHoBTLMhYS2Gb51gWxpeIq8knRmPnYePbF5MOkR0Zkly4zKH7s1dE=\"";
+
+ assertEquals(signatureHeader, expectedHeader);
+
+ // Verify that the request signature can be verified by the filter
+ MessageVerifier messageVerifier = new MessageVerifier(keyId -> publicKey);
+ messageVerifier.setAddDefaultRequiredHeaders(false);
+ messageVerifier.setSecurityProvider(new MockSecurityProvider());
+ messageVerifier.setAlgorithmProvider(new MockAlgorithmProvider());
+
+ VerifySignatureFilter verifySignatureFilter = new VerifySignatureFilter();
+ verifySignatureFilter.setMessageVerifier(messageVerifier);
+
+ ContainerRequestContext containerRequestContext =
+ getContainerRequestContextMock(uri, method, requestStringHeaders);
+ InputStream stream = new ByteArrayInputStream("{\"hello\": \"world\"}".getBytes(StandardCharsets.UTF_8));
+ when(containerRequestContext.getEntityStream()).thenReturn(stream);
+
+ verifySignatureFilter.filter(containerRequestContext);
+ }
+
+ private ClientRequestContext getClientRequestContextMock(URI uri, String method,
+ MultivaluedMap<String, Object> requestHeaders) {
+ ClientRequestContext requestContext = mock(ClientRequestContext.class);
+ when(requestContext.getEntity()).thenReturn(null);
+ when(requestContext.getMethod()).thenReturn(method);
+ when(requestContext.getHeaders()).thenReturn(requestHeaders);
+ when(requestContext.getUri()).thenReturn(uri);
+ return requestContext;
+ }
+
+ private ContainerRequestContext getContainerRequestContextMock(URI uri, String method,
+ MultivaluedMap<String, String>
+ requestStringHeaders) {
+ ContainerRequestContext containerRequestContext = mock(ContainerRequestContext.class);
+ UriInfo uriInfo = mock(UriInfo.class);
+ when(uriInfo.getAbsolutePath()).thenReturn(uri);
+ when(containerRequestContext.getUriInfo()).thenReturn(uriInfo);
+ when(containerRequestContext.getMethod()).thenReturn(method);
+ when(containerRequestContext.getHeaders()).thenReturn(requestStringHeaders);
+ return containerRequestContext;
+ }
+
private static Map<String, List<String>> createMockHeaders() {
Map<String, List<String>> headers = new HashMap<>();
headers.put("Host", Collections.singletonList("example.com"));