This is an automated email from the ASF dual-hosted git repository. coheigea pushed a commit to branch 3.2.x-fixes in repository https://gitbox.apache.org/repos/asf/cxf.git
commit 8e432a28ef8bbb4278cafb346b32ab1d9547b045 Author: Colm O hEigeartaigh <[email protected]> AuthorDate: Tue May 7 12:27:20 2019 +0100 CXF-8035 - Checking on null values in HTTP Header for protected JWS header (cherry picked from commit c79d8ac83379b9f80e124823fbb569141cc5e608) # Conflicts: # rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JoseJaxrsUtils.java --- .../cxf/rs/security/jose/jaxrs/JoseJaxrsUtils.java | 20 +- .../jose/jwejws/BookServerHTTPHeaders.java | 59 +++++ .../security/jose/jwejws/JwsHTTPHeaderTest.java | 268 +++++++++++++++++++++ .../security/jose/jwejws/http-headers-server.xml | 79 ++++++ 4 files changed, 416 insertions(+), 10 deletions(-) diff --git a/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JoseJaxrsUtils.java b/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JoseJaxrsUtils.java index b1f1a38..1f2cc23 100644 --- a/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JoseJaxrsUtils.java +++ b/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JoseJaxrsUtils.java @@ -33,13 +33,13 @@ import org.apache.cxf.rs.security.jose.common.JoseHeaders; public final class JoseJaxrsUtils { private static final String HTTP_PREFIX = "http."; - private static final Set<String> DEFAULT_PROTECTED_HTTP_HEADERS = + private static final Set<String> DEFAULT_PROTECTED_HTTP_HEADERS = new HashSet<String>(Arrays.asList(HttpHeaders.CONTENT_TYPE, HttpHeaders.ACCEPT)); - + private JoseJaxrsUtils() { } - + public static void protectHttpHeaders(MultivaluedMap<String, Object> httpHeaders, JoseHeaders joseHeaders, Set<String> protectedHttpHeaders) { @@ -56,7 +56,7 @@ public final class JoseJaxrsUtils { } } private static String getJoseHeaderValue(List<? extends Object> headerValues) { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new StringBuilder(); for (Object o : headerValues) { String[] parts = o.toString().split(","); for (String part : parts) { @@ -66,7 +66,7 @@ public final class JoseJaxrsUtils { return sb.toString(); } - public static void validateHttpHeaders(MultivaluedMap<String, String> httpHeaders, + public static void validateHttpHeaders(MultivaluedMap<String, String> httpHeaders, JoseHeaders joseHeaders, Set<String> protectedHttpHeaders) { if (protectedHttpHeaders == null) { @@ -76,19 +76,19 @@ public final class JoseJaxrsUtils { Map<String, String> updatedHttpHeaders = new HashMap<String, String>(); for (String headerName : protectedHttpHeaders) { List<String> headerValues = httpHeaders.get(headerName); - if (headerValues != null) { + if (headerValues != null && !headerValues.isEmpty() && headerValues.get(0) != null) { String headerValue = getJoseHeaderValue(headerValues); String prefixedHeaderName = HTTP_PREFIX + headerName; updatedHttpHeaders.put(prefixedHeaderName, headerValue); String joseHeaderValue = joseHeaders.getStringProperty(prefixedHeaderName); if (joseHeaderValue != null) { joseHttpHeaders.put(prefixedHeaderName, joseHeaderValue); - } + } } - + } - if (joseHttpHeaders.size() != updatedHttpHeaders.size() - || !joseHttpHeaders.entrySet().containsAll(updatedHttpHeaders.entrySet())) { + if (joseHttpHeaders.size() != updatedHttpHeaders.size() + || !joseHttpHeaders.entrySet().containsAll(updatedHttpHeaders.entrySet())) { throw ExceptionUtils.toBadRequestException(null, null); } } diff --git a/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/jose/jwejws/BookServerHTTPHeaders.java b/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/jose/jwejws/BookServerHTTPHeaders.java new file mode 100644 index 0000000..02462e4 --- /dev/null +++ b/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/jose/jwejws/BookServerHTTPHeaders.java @@ -0,0 +1,59 @@ +/** + * 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.systest.jaxrs.security.jose.jwejws; + +import java.net.URL; + +import org.apache.cxf.Bus; +import org.apache.cxf.BusFactory; +import org.apache.cxf.bus.spring.SpringBusFactory; +import org.apache.cxf.testutil.common.AbstractBusTestServerBase; +import org.apache.cxf.testutil.common.TestUtil; + +public class BookServerHTTPHeaders extends AbstractBusTestServerBase { + public static final String PORT = TestUtil.getPortNumber("jaxrs-jose-httpheaders"); + private static final URL SERVER_CONFIG_FILE = + BookServerHTTPHeaders.class.getResource("http-headers-server.xml"); + + protected void run() { + SpringBusFactory bf = new SpringBusFactory(); + Bus springBus = bf.createBus(SERVER_CONFIG_FILE); + BusFactory.setDefaultBus(springBus); + setBus(springBus); + + try { + new BookServerHTTPHeaders(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void main(String[] args) { + try { + BookServerHTTPHeaders s = new BookServerHTTPHeaders(); + s.start(); + } catch (Exception ex) { + ex.printStackTrace(); + System.exit(-1); + } finally { + System.out.println("done!"); + } + } +} diff --git a/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/jose/jwejws/JwsHTTPHeaderTest.java b/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/jose/jwejws/JwsHTTPHeaderTest.java new file mode 100644 index 0000000..a91411e --- /dev/null +++ b/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/jose/jwejws/JwsHTTPHeaderTest.java @@ -0,0 +1,268 @@ +/** + * 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.systest.jaxrs.security.jose.jwejws; + +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; + +import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider; + +import org.apache.cxf.interceptor.Fault; +import org.apache.cxf.jaxrs.client.WebClient; +import org.apache.cxf.message.Message; +import org.apache.cxf.phase.AbstractPhaseInterceptor; +import org.apache.cxf.phase.Phase; +import org.apache.cxf.rs.security.jose.jaxrs.JwsWriterInterceptor; +import org.apache.cxf.systest.jaxrs.security.Book; +import org.apache.cxf.testutil.common.AbstractBusClientServerTestBase; + +import org.junit.BeforeClass; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +/** + * Some signature tests for signing HTTP Headers + */ +public class JwsHTTPHeaderTest extends AbstractBusClientServerTestBase { + public static final String PORT = BookServerHTTPHeaders.PORT; + + @BeforeClass + public static void startServers() throws Exception { + assertTrue("server did not launch correctly", + launchServer(BookServerHTTPHeaders.class, true)); + } + + @org.junit.Test + public void testSignHTTPHeaders() throws Exception { + + URL busFile = JwsHTTPHeaderTest.class.getResource("client.xml"); + + List<Object> providers = new ArrayList<>(); + providers.add(new JacksonJsonProvider()); + JwsWriterInterceptor jwsWriterInterceptor = new JwsWriterInterceptor(); + providers.add(jwsWriterInterceptor); + + String address = "http://localhost:" + PORT + "/jwsheaderdefault/bookstore/books"; + WebClient client = + WebClient.create(address, providers, busFile.toString()); + client.type("application/json").accept("application/json"); + + Map<String, Object> properties = new HashMap<>(); + properties.put("rs.security.keystore.type", "jwk"); + properties.put("rs.security.keystore.alias", "2011-04-29"); + properties.put("rs.security.keystore.file", + "org/apache/cxf/systest/jaxrs/security/certs/jwkPrivateSet.txt"); + properties.put("rs.security.signature.algorithm", "RS256"); + WebClient.getConfig(client).getRequestContext().putAll(properties); + + // Expect failure on not signing the default headers + Response response = client.post(new Book("book", 123L)); + assertNotEquals(response.getStatus(), 200); + + jwsWriterInterceptor.setProtectHttpHeaders(true); + response = client.post(new Book("book", 123L)); + assertEquals(response.getStatus(), 200); + } + + @org.junit.Test + public void testSpecifyHeadersToSign() throws Exception { + + URL busFile = JwsHTTPHeaderTest.class.getResource("client.xml"); + + List<Object> providers = new ArrayList<>(); + providers.add(new JacksonJsonProvider()); + JwsWriterInterceptor jwsWriterInterceptor = new JwsWriterInterceptor(); + jwsWriterInterceptor.setProtectHttpHeaders(true); + Set<String> headersToSign = new HashSet<>(); + headersToSign.add(HttpHeaders.CONTENT_TYPE); + jwsWriterInterceptor.setProtectedHttpHeaders(headersToSign); + providers.add(jwsWriterInterceptor); + + String address = "http://localhost:" + PORT + "/jwsheaderdefault/bookstore/books"; + WebClient client = + WebClient.create(address, providers, busFile.toString()); + client.type("application/json").accept("application/json"); + + Map<String, Object> properties = new HashMap<>(); + properties.put("rs.security.keystore.type", "jwk"); + properties.put("rs.security.keystore.alias", "2011-04-29"); + properties.put("rs.security.keystore.file", + "org/apache/cxf/systest/jaxrs/security/certs/jwkPrivateSet.txt"); + properties.put("rs.security.signature.algorithm", "RS256"); + WebClient.getConfig(client).getRequestContext().putAll(properties); + + // Expect failure on not signing all of the default headers + Response response = client.post(new Book("book", 123L)); + assertNotEquals(response.getStatus(), 200); + + headersToSign.add(HttpHeaders.ACCEPT); + response = client.post(new Book("book", 123L)); + assertEquals(response.getStatus(), 200); + } + + @org.junit.Test + public void testSignAdditionalCustomHeader() throws Exception { + + URL busFile = JwsHTTPHeaderTest.class.getResource("client.xml"); + + List<Object> providers = new ArrayList<>(); + providers.add(new JacksonJsonProvider()); + JwsWriterInterceptor jwsWriterInterceptor = new JwsWriterInterceptor(); + jwsWriterInterceptor.setProtectHttpHeaders(true); + Set<String> headersToSign = new HashSet<>(); + headersToSign.add(HttpHeaders.CONTENT_TYPE); + headersToSign.add(HttpHeaders.ACCEPT); + headersToSign.add("customheader"); + jwsWriterInterceptor.setProtectedHttpHeaders(headersToSign); + providers.add(jwsWriterInterceptor); + + String address = "http://localhost:" + PORT + "/jwsheaderdefault/bookstore/books"; + WebClient client = + WebClient.create(address, providers, busFile.toString()); + client.type("application/json").accept("application/json"); + + Map<String, Object> properties = new HashMap<>(); + properties.put("rs.security.keystore.type", "jwk"); + properties.put("rs.security.keystore.alias", "2011-04-29"); + properties.put("rs.security.keystore.file", + "org/apache/cxf/systest/jaxrs/security/certs/jwkPrivateSet.txt"); + properties.put("rs.security.signature.algorithm", "RS256"); + WebClient.getConfig(client).getRequestContext().putAll(properties); + WebClient.getConfig(client).getOutInterceptors().add(new CustomHeaderInterceptor(Phase.PRE_STREAM)); + + Response response = client.post(new Book("book", 123L)); + response = client.post(new Book("book", 123L)); + assertEquals(response.getStatus(), 200); + } + + @org.junit.Test + public void testSignCustomHeaderRequired() throws Exception { + + URL busFile = JwsHTTPHeaderTest.class.getResource("client.xml"); + + List<Object> providers = new ArrayList<>(); + providers.add(new JacksonJsonProvider()); + JwsWriterInterceptor jwsWriterInterceptor = new JwsWriterInterceptor(); + jwsWriterInterceptor.setProtectHttpHeaders(true); + providers.add(jwsWriterInterceptor); + + String address = "http://localhost:" + PORT + "/jwsheadercustom/bookstore/books"; + WebClient client = + WebClient.create(address, providers, busFile.toString()); + client.type("application/json").accept("application/json"); + + Map<String, Object> properties = new HashMap<>(); + properties.put("rs.security.keystore.type", "jwk"); + properties.put("rs.security.keystore.alias", "2011-04-29"); + properties.put("rs.security.keystore.file", + "org/apache/cxf/systest/jaxrs/security/certs/jwkPrivateSet.txt"); + properties.put("rs.security.signature.algorithm", "RS256"); + WebClient.getConfig(client).getRequestContext().putAll(properties); + WebClient.getConfig(client).getOutInterceptors().add(new CustomHeaderInterceptor(Phase.PRE_STREAM)); + + // Expect failure on not signing all of the required headers + Response response = client.post(new Book("book", 123L)); + assertNotEquals(response.getStatus(), 200); + + Set<String> headersToSign = new HashSet<>(); + headersToSign.add(HttpHeaders.CONTENT_TYPE); + headersToSign.add(HttpHeaders.ACCEPT); + headersToSign.add("customheader"); + jwsWriterInterceptor.setProtectedHttpHeaders(headersToSign); + + response = client.post(new Book("book", 123L)); + response = client.post(new Book("book", 123L)); + assertEquals(response.getStatus(), 200); + } + + @org.junit.Test + public void testSignEmptyCustomHeader() throws Exception { + + URL busFile = JwsHTTPHeaderTest.class.getResource("client.xml"); + + List<Object> providers = new ArrayList<>(); + providers.add(new JacksonJsonProvider()); + JwsWriterInterceptor jwsWriterInterceptor = new JwsWriterInterceptor(); + jwsWriterInterceptor.setProtectHttpHeaders(true); + Set<String> headersToSign = new HashSet<>(); + headersToSign.add(HttpHeaders.CONTENT_TYPE); + headersToSign.add(HttpHeaders.ACCEPT); + headersToSign.add("customheader"); + jwsWriterInterceptor.setProtectedHttpHeaders(headersToSign); + providers.add(jwsWriterInterceptor); + + String address = "http://localhost:" + PORT + "/jwsheadercustom/bookstore/books"; + WebClient client = + WebClient.create(address, providers, busFile.toString()); + client.type("application/json").accept("application/json"); + + Map<String, Object> properties = new HashMap<>(); + properties.put("rs.security.keystore.type", "jwk"); + properties.put("rs.security.keystore.alias", "2011-04-29"); + properties.put("rs.security.keystore.file", + "org/apache/cxf/systest/jaxrs/security/certs/jwkPrivateSet.txt"); + properties.put("rs.security.signature.algorithm", "RS256"); + WebClient.getConfig(client).getRequestContext().putAll(properties); + CustomHeaderInterceptor customHeaderInterceptor = new CustomHeaderInterceptor(Phase.PRE_STREAM); + customHeaderInterceptor.setEmpty(true); + WebClient.getConfig(client).getOutInterceptors().add(customHeaderInterceptor); + + Response response = client.post(new Book("book", 123L)); + response = client.post(new Book("book", 123L)); + assertEquals(response.getStatus(), 200); + } + + private static class CustomHeaderInterceptor extends AbstractPhaseInterceptor<Message> { + + private boolean empty; + + CustomHeaderInterceptor(String phase) { + super(phase); + } + + @Override + public void handleMessage(Message message) throws Fault { + @SuppressWarnings("unchecked") + Map<String, List<?>> headers = (Map<String, List<?>>) message.get(Message.PROTOCOL_HEADERS); + headers.put("customheader", empty ? Arrays.asList("") : Arrays.asList("value1", "value2")); + } + + public boolean isEmpty() { + return empty; + } + + public void setEmpty(boolean empty) { + this.empty = empty; + } + + } + +} diff --git a/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/jose/jwejws/http-headers-server.xml b/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/jose/jwejws/http-headers-server.xml new file mode 100644 index 0000000..311d990 --- /dev/null +++ b/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/jose/jwejws/http-headers-server.xml @@ -0,0 +1,79 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +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. +--> +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:http="http://cxf.apache.org/transports/http/configuration" + xmlns:httpj="http://cxf.apache.org/transports/http-jetty/configuration" + xmlns:sec="http://cxf.apache.org/configuration/security" + xmlns:cxf="http://cxf.apache.org/core" + xmlns:jaxrs="http://cxf.apache.org/jaxrs" + xsi:schemaLocation="http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://cxf.apache.org/transports/http/configuration http://cxf.apache.org/schemas/configuration/http-conf.xsd http://cxf.apache.org/transports/http-jetty/configuration http://cxf.apache.org/schemas/configuration/http-jetty.xsd ht [...] + <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"/> + <cxf:bus> + <cxf:features> + <cxf:logging/> + </cxf:features> + </cxf:bus> + + <bean id="serviceBean" class="org.apache.cxf.systest.jaxrs.security.jose.BookStore"/> + + <bean id="jwsInFilter" class="org.apache.cxf.rs.security.jose.jaxrs.JwsContainerRequestFilter"> + <property name="validateHttpHeaders" value="true" /> + </bean> + + <jaxrs:server address="http://localhost:${testutil.ports.jaxrs-jose-httpheaders}/jwsheaderdefault"> + <jaxrs:serviceBeans> + <ref bean="serviceBean"/> + </jaxrs:serviceBeans> + <jaxrs:providers> + <ref bean="jwsInFilter"/> + </jaxrs:providers> + <jaxrs:properties> + <entry key="rs.security.signature.in.properties" + value="org/apache/cxf/systest/jaxrs/security/bob.jwk.properties"/> + </jaxrs:properties> + </jaxrs:server> + + <bean id="customHeaderJwsInFilter" class="org.apache.cxf.rs.security.jose.jaxrs.JwsContainerRequestFilter"> + <property name="validateHttpHeaders" value="true" /> + <property name="protectedHttpHeaders"> + <set> + <value>Content-Type</value> + <value>Accept</value> + <value>customheader</value> + </set> + </property> + </bean> + + <jaxrs:server address="http://localhost:${testutil.ports.jaxrs-jose-httpheaders}/jwsheadercustom"> + <jaxrs:serviceBeans> + <ref bean="serviceBean"/> + </jaxrs:serviceBeans> + <jaxrs:providers> + <ref bean="customHeaderJwsInFilter"/> + </jaxrs:providers> + <jaxrs:properties> + <entry key="rs.security.signature.in.properties" + value="org/apache/cxf/systest/jaxrs/security/bob.jwk.properties"/> + </jaxrs:properties> + </jaxrs:server> + + +</beans> \ No newline at end of file
