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"/>