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 2019/11/14 17:27:52 UTC
[cxf-fediz] branch master updated: Porting CXF JWK changes
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-fediz.git
The following commit(s) were added to refs/heads/master by this push:
new 3510c47 Porting CXF JWK changes
3510c47 is described below
commit 3510c477c8caba39bdd70c635cbacc1ce9dd2621
Author: Colm O hEigeartaigh <co...@apache.org>
AuthorDate: Thu Nov 14 17:27:28 2019 +0000
Porting CXF JWK changes
---
.../fediz/service/oidc/FedizOidcKeysService.java | 165 +++++++++++++++++++++
.../src/main/webapp/WEB-INF/applicationContext.xml | 2 +-
.../cxf/fediz/systests/oidc/AbstractOIDCTest.java | 37 +++++
systests/oidc/src/test/resources/jwkPrivateSet.txt | 43 ++++++
.../src/test/resources/oidc/applicationContext.xml | 17 ++-
.../resources/oidc/spring/applicationContext.xml | 17 ++-
6 files changed, 278 insertions(+), 3 deletions(-)
diff --git a/services/oidc/src/main/java/org/apache/cxf/fediz/service/oidc/FedizOidcKeysService.java b/services/oidc/src/main/java/org/apache/cxf/fediz/service/oidc/FedizOidcKeysService.java
new file mode 100644
index 0000000..65468e5
--- /dev/null
+++ b/services/oidc/src/main/java/org/apache/cxf/fediz/service/oidc/FedizOidcKeysService.java
@@ -0,0 +1,165 @@
+/**
+ * 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.fediz.service.oidc;
+
+import java.security.PublicKey;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+import org.apache.cxf.common.util.PropertyUtils;
+import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.cxf.jaxrs.utils.JAXRSUtils;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.rs.security.jose.common.JoseConstants;
+import org.apache.cxf.rs.security.jose.common.JoseException;
+import org.apache.cxf.rs.security.jose.common.KeyManagementUtils;
+import org.apache.cxf.rs.security.jose.jwk.JsonWebKey;
+import org.apache.cxf.rs.security.jose.jwk.JsonWebKeys;
+import org.apache.cxf.rs.security.jose.jwk.JwkUtils;
+import org.apache.cxf.rs.security.jose.jwk.KeyOperation;
+import org.apache.cxf.rs.security.jose.jwk.KeyType;
+import org.apache.cxf.rs.security.jose.jwk.PublicKeyUse;
+import org.apache.cxf.rs.security.jose.jws.JwsUtils;
+
+/**
+ * TODO Remove this once we pick up CXF 3.3.5
+ */
+@Path("keys")
+public class FedizOidcKeysService {
+
+ private volatile JsonWebKeys keySet;
+ private WebClient keyServiceClient;
+ private boolean stripPrivateParameters = true;
+
+ @GET
+ @Produces("application/json")
+ public JsonWebKeys getPublicVerificationKeys() {
+ if (keySet == null) {
+ if (keyServiceClient == null) {
+ keySet = getFromLocalStore(stripPrivateParameters);
+ } else {
+ keySet = keyServiceClient.get(JsonWebKeys.class);
+ }
+
+ }
+ return keySet;
+ }
+
+ private static JsonWebKeys getFromLocalStore(boolean stripPrivateParameters) {
+ Properties props = JwsUtils.loadSignatureInProperties(true);
+ return loadPublicVerificationKeys(JAXRSUtils.getCurrentMessage(), props, stripPrivateParameters);
+ }
+
+ public void setKeyServiceClient(WebClient keyServiceClient) {
+ this.keyServiceClient = keyServiceClient;
+ }
+
+ public boolean isStripPrivateParameters() {
+ return stripPrivateParameters;
+ }
+
+ /**
+ * Whether to strip private parameters from the keys that are returned. The default is true.
+ */
+ public void setStripPrivateParameters(boolean stripPrivateParameters) {
+ this.stripPrivateParameters = stripPrivateParameters;
+ }
+
+ private static JsonWebKeys loadPublicVerificationKeys(Message m, Properties props, boolean stripPrivateParameters) {
+ String storeType = props.getProperty(JoseConstants.RSSEC_KEY_STORE_TYPE);
+ if ("jwk".equals(storeType)) {
+ List<JsonWebKey> jsonWebKeys = loadJsonWebKeys(m, props, KeyOperation.SIGN);
+ if (jsonWebKeys == null || jsonWebKeys.isEmpty()) {
+ throw new JoseException("Error loading keys");
+ }
+ JsonWebKeys retKeys = new JsonWebKeys();
+ retKeys.setKeys(stripPrivateParameters ? stripPrivateParameters(jsonWebKeys) : jsonWebKeys);
+ return retKeys;
+ }
+ X509Certificate[] certs = null;
+ if (PropertyUtils.isTrue(props.get(JoseConstants.RSSEC_SIGNATURE_INCLUDE_CERT))) {
+ certs = KeyManagementUtils.loadX509CertificateOrChain(m, props);
+ }
+ PublicKey key = certs != null && certs.length > 0
+ ? certs[0].getPublicKey() : KeyManagementUtils.loadPublicKey(m, props);
+ JsonWebKey jwk = JwkUtils.fromPublicKey(key, props, JoseConstants.RSSEC_SIGNATURE_ALGORITHM);
+ jwk.setPublicKeyUse(PublicKeyUse.SIGN);
+ if (certs != null) {
+ jwk.setX509Chain(KeyManagementUtils.encodeX509CertificateChain(certs));
+ }
+ return new JsonWebKeys(jwk);
+ }
+
+ private static List<JsonWebKey> stripPrivateParameters(List<JsonWebKey> keys) {
+ if (keys == null) {
+ return Collections.emptyList();
+ }
+
+ List<JsonWebKey> parsedKeys = new ArrayList<>(keys.size());
+ Iterator<JsonWebKey> iter = keys.iterator();
+ while (iter.hasNext()) {
+ JsonWebKey key = iter.next();
+ if (!(key.containsProperty("k") || key.getKeyType() == KeyType.OCTET)) {
+ // We don't allow secret keys in a public keyset
+ key.removeProperty(JsonWebKey.RSA_PRIVATE_EXP);
+ key.removeProperty(JsonWebKey.RSA_FIRST_PRIME_FACTOR);
+ key.removeProperty(JsonWebKey.RSA_SECOND_PRIME_FACTOR);
+ key.removeProperty(JsonWebKey.RSA_FIRST_PRIME_CRT);
+ key.removeProperty(JsonWebKey.RSA_SECOND_PRIME_CRT);
+ key.removeProperty(JsonWebKey.RSA_FIRST_CRT_COEFFICIENT);
+ parsedKeys.add(key);
+ }
+ }
+ return parsedKeys;
+ }
+
+ private static List<JsonWebKey> loadJsonWebKeys(Message m,
+ Properties props,
+ KeyOperation keyOper) {
+ JsonWebKeys jwkSet = JwkUtils.loadJwkSet(m, props, null);
+ String kid = KeyManagementUtils.getKeyId(m, props, JoseConstants.RSSEC_KEY_STORE_ALIAS, keyOper);
+ if (kid != null) {
+ return Collections.singletonList(jwkSet.getKey(kid));
+ }
+ String kids = KeyManagementUtils.getKeyId(m, props, JoseConstants.RSSEC_KEY_STORE_ALIASES, keyOper);
+ if (kids != null) {
+ String[] values = kids.split(",");
+ List<JsonWebKey> keys = new ArrayList<>(values.length);
+ for (String value : values) {
+ keys.add(jwkSet.getKey(value));
+ }
+ return keys;
+ }
+ if (keyOper != null) {
+ List<JsonWebKey> keys = jwkSet.getKeyOperationMap().get(keyOper);
+ if (keys != null && keys.size() == 1) {
+ return Collections.singletonList(keys.get(0));
+ }
+ }
+ return null;
+ }
+}
diff --git a/services/oidc/src/main/webapp/WEB-INF/applicationContext.xml b/services/oidc/src/main/webapp/WEB-INF/applicationContext.xml
index b2ee2fe..e065b23 100644
--- a/services/oidc/src/main/webapp/WEB-INF/applicationContext.xml
+++ b/services/oidc/src/main/webapp/WEB-INF/applicationContext.xml
@@ -104,7 +104,7 @@
Public JWK Key Service: Disable it if the client secret is used or if
pre-installing public OIDC keys to clients is preferred
-->
- <bean id="oidcKeysService" class="org.apache.cxf.rs.security.oidc.idp.OidcKeysService"/>
+ <bean id="oidcKeysService" class="org.apache.cxf.fediz.service.oidc.FedizOidcKeysService"/>
<jaxrs:server address="/jwk">
<jaxrs:serviceBeans>
<ref bean="oidcKeysService"/>
diff --git a/systests/oidc/src/test/java/org/apache/cxf/fediz/systests/oidc/AbstractOIDCTest.java b/systests/oidc/src/test/java/org/apache/cxf/fediz/systests/oidc/AbstractOIDCTest.java
index ffdd055..1535de9 100644
--- a/systests/oidc/src/test/java/org/apache/cxf/fediz/systests/oidc/AbstractOIDCTest.java
+++ b/systests/oidc/src/test/java/org/apache/cxf/fediz/systests/oidc/AbstractOIDCTest.java
@@ -1114,6 +1114,43 @@ abstract class AbstractOIDCTest {
webClient2.close();
}
+ @org.junit.Test
+ public void testJWKKeyService() throws Exception {
+
+ String url = "https://localhost:" + getRpHttpsPort() + "/" + getServletContextName() + "/jwk/keys";
+ String user = "alice";
+ String password = "ecila";
+
+ WebClient webClient = setupWebClient(user, password);
+ final UnexpectedPage responsePage = webClient.getPage(url);
+ String response = responsePage.getWebResponse().getContentAsString();
+ assertTrue(response.contains("alice"));
+ assertTrue(response.contains("RSA"));
+ assertTrue(response.contains("\"e\":"));
+ assertFalse(response.contains("\"d\":"));
+
+ webClient.close();
+ }
+
+ @org.junit.Test
+ public void testJWKKeyService2() throws Exception {
+
+ String url = "https://localhost:" + getRpHttpsPort() + "/" + getServletContextName() + "/jwk2/keys";
+ String user = "alice";
+ String password = "ecila";
+
+ WebClient webClient = setupWebClient(user, password);
+ final UnexpectedPage responsePage = webClient.getPage(url);
+ String response = responsePage.getWebResponse().getContentAsString();
+ System.out.println("RESP: " + response);
+ assertTrue(response.contains("2011-04-29"));
+ assertTrue(response.contains("RSA"));
+ assertTrue(response.contains("\"e\":"));
+ assertFalse(response.contains("\"d\":"));
+
+ webClient.close();
+ }
+
private static WebClient setupWebClient(String user, String password) {
final WebClient webClient = new WebClient();
webClient.getOptions().setUseInsecureSSL(true);
diff --git a/systests/oidc/src/test/resources/jwkPrivateSet.txt b/systests/oidc/src/test/resources/jwkPrivateSet.txt
new file mode 100644
index 0000000..5dab671
--- /dev/null
+++ b/systests/oidc/src/test/resources/jwkPrivateSet.txt
@@ -0,0 +1,43 @@
+{"keys":
+ [
+ {"kty":"RSA",
+ "n":"oahUIoWw0K0usKNuOR6H4wkf4oBUXHTxRvgb48E-BVvxkeDNjbC4he8rUWcJoZmds2h7M70imEVhRU5djINXtqllXI4DFqcI1DgjT9LewND8MW2Krf3Spsk_ZkoFnilakGygTwpZ3uesH-PFABNIUYpOiN15dsQRkgr0vEhxN92i2asbOenSZeyaxziK72UwxrrKoExv6kc5twXTq4h-QChLOln0_mtUZwfsRaMStPs6mS6XrgxnxbWhojf663tuEQueGC-FCMfra36C9knDFGzKsNa7LZK2djYgyD3JR_MB_4NUJW_TqOQtwHYbxevoJArm-L5StowjzGy-_bq6Gw",
+ "e":"AQAB",
+ "d":"kLdtIj6GbDks_ApCSTYQtelcNttlKiOyPzMrXHeI-yk1F7-kpDxY4-WY5NWV5KntaEeXS1j82E375xxhWMHXyvjYecPT9fpwR_M9gV8n9Hrh2anTpTD93Dt62ypW3yDsJzBnTnrYu1iwWRgBKrEYY46qAZIrA2xAwnm2X7uGR1hghkqDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vlt3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSndVTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ",
+ "kid":"2011-04-29"},
+
+ {"kty":"oct",
+ "alg":"A128KW",
+ "k":"GawgguFyGrWKav7AX4VKUg",
+ "kid":"AesWrapKey"},
+
+ {"kty":"oct",
+ "alg":"A128KW",
+ "k":"GawgguFyGrWKav7AX4VKUg",
+ "kid":"AesWrapKey2"},
+
+ {"kty":"oct",
+ "alg":"A128GCM",
+ "k":"GawgguFyGrWKav7AX4VKUg",
+ "kid":"AesGcmKey"},
+
+
+ {"kty":"oct",
+ "alg":"HS256",
+ "k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow",
+ "kid":"HMACKey"},
+
+ {"kty":"oct",
+ "alg":"HS512",
+ "k":"SFM1MTJTZWNyZXRLZXlUb1NpZ25Db250ZW50LkhTNTEyU2VjcmV0S2V5VG9TaWduQ29udGVudC5IUzUxMlNlY3JldEtleVRvU2lnbkNvbnRlbnQuSFM1MTJTZWNyZXRLZXlUb1NpZ25Db250ZW50LkhTNTEyU2VjcmV0S2V5VG9TaWduQ29udGVudC5IUzUxMlNlY3JldEtleVRvU2lnbkNvbnRlbnQuSFM1MTJTZWNyZXRLZXlUb1NpZ25Db250ZW50LkhTNTEyU2VjcmV0S2V5VG9TaWduQ29udGVudC5IUzUxMlNlY3JldEtleVRvU2lnbkNvbnRlbnQuSFM1MTJTZWNyZXRLZXlUb1NpZ25Db250ZW50LkhTNTEyU2VjcmV0S2V5VG9TaWduQ29udGVudC5IUzUxMlNlY3JldEtleVRvU2lnbkNvbnRlbnQuSFM1MTJTZWNyZXRLZXlUb1NpZ25D [...]
+ "kid":"HMAC512Key"},
+
+ {"kty":"EC",
+ "crv":"P-256",
+ "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
+ "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
+ "d":"870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE",
+ "use":"enc",
+ "kid":"ECKey"}
+ ]
+ }
diff --git a/systests/oidc/src/test/resources/oidc/applicationContext.xml b/systests/oidc/src/test/resources/oidc/applicationContext.xml
index c22a4e6..89bf21c 100644
--- a/systests/oidc/src/test/resources/oidc/applicationContext.xml
+++ b/systests/oidc/src/test/resources/oidc/applicationContext.xml
@@ -110,7 +110,7 @@
Public JWK Key Service: Disable it if the client secret is used or if
pre-installing public OIDC keys to clients is preferred
-->
- <bean id="oidcKeysService" class="org.apache.cxf.rs.security.oidc.idp.OidcKeysService"/>
+ <bean id="oidcKeysService" class="org.apache.cxf.fediz.service.oidc.FedizOidcKeysService"/>
<jaxrs:server address="/jwk">
<jaxrs:serviceBeans>
<ref bean="oidcKeysService"/>
@@ -125,6 +125,21 @@
</jaxrs:properties>
</jaxrs:server>
+ <bean id="oidcKeysService2" class="org.apache.cxf.fediz.service.oidc.FedizOidcKeysService"/>
+ <jaxrs:server address="/jwk2">
+ <jaxrs:serviceBeans>
+ <ref bean="oidcKeysService2"/>
+ </jaxrs:serviceBeans>
+ <jaxrs:providers>
+ <ref bean="corsFilter"/>
+ <bean class="org.apache.cxf.rs.security.jose.jaxrs.JsonWebKeysProvider"/>
+ </jaxrs:providers>
+ <jaxrs:properties>
+ <entry key="rs.security.keystore.type" value="jwk" />
+ <entry key="rs.security.keystore.alias" value="2011-04-29"/>
+ <entry key="rs.security.keystore.file" value="jwkPrivateSet.txt" />
+ </jaxrs:properties>
+ </jaxrs:server>
<bean id="oauth2TokenValidationFilter" class="org.apache.cxf.rs.security.oauth2.filters.OAuthRequestFilter">
<property name="dataProvider" ref="oauthProvider"/>
diff --git a/systests/oidc/src/test/resources/oidc/spring/applicationContext.xml b/systests/oidc/src/test/resources/oidc/spring/applicationContext.xml
index 35bf5a5..e2cdc7d 100644
--- a/systests/oidc/src/test/resources/oidc/spring/applicationContext.xml
+++ b/systests/oidc/src/test/resources/oidc/spring/applicationContext.xml
@@ -178,7 +178,7 @@
Public JWK Key Service: Disable it if the client secret is used or if
pre-installing public OIDC keys to clients is preferred
-->
- <bean id="oidcKeysService" class="org.apache.cxf.rs.security.oidc.idp.OidcKeysService"/>
+ <bean id="oidcKeysService" class="org.apache.cxf.fediz.service.oidc.FedizOidcKeysService"/>
<jaxrs:server address="/jwk">
<jaxrs:serviceBeans>
<ref bean="oidcKeysService"/>
@@ -193,6 +193,21 @@
</jaxrs:properties>
</jaxrs:server>
+ <bean id="oidcKeysService2" class="org.apache.cxf.fediz.service.oidc.FedizOidcKeysService"/>
+ <jaxrs:server address="/jwk2">
+ <jaxrs:serviceBeans>
+ <ref bean="oidcKeysService2"/>
+ </jaxrs:serviceBeans>
+ <jaxrs:providers>
+ <ref bean="corsFilter"/>
+ <bean class="org.apache.cxf.rs.security.jose.jaxrs.JsonWebKeysProvider"/>
+ </jaxrs:providers>
+ <jaxrs:properties>
+ <entry key="rs.security.keystore.type" value="jwk" />
+ <entry key="rs.security.keystore.alias" value="2011-04-29"/>
+ <entry key="rs.security.keystore.file" value="jwkPrivateSet.txt" />
+ </jaxrs:properties>
+ </jaxrs:server>
<bean id="oauth2TokenValidationFilter" class="org.apache.cxf.rs.security.oauth2.filters.OAuthRequestFilter">
<property name="dataProvider" ref="oauthProvider"/>