[java-idp-oidc] branch main updated: JOIDC-303 - Improve incoming JWKS verification in dynamic client registration

Codeberg noreply at shibboleth.net
Fri Nov 6 12:16:06 UTC 2026


This is an automated email from the git hooks/post-receive script.

codeberg pushed a commit to branch main
in repository java-idp-oidc.

View the commit online:
https://codeberg.org/Shibboleth/java-idp-oidc/commit/40737b13e3011bf91cabdff8202fe833b4730284

The following commit(s) were added to refs/heads/main by this push:
     new 40737b13 JOIDC-303 - Improve incoming JWKS verification in dynamic client registration
40737b13 is described below

commit 40737b13e3011bf91cabdff8202fe833b4730284
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri Nov 6 14:14:32 2026 +0200

    JOIDC-303 - Improve incoming JWKS verification in dynamic client registration
    
    https://shibboleth.atlassian.net/browse/JOIDC-303
    
    - Perform the public key check for both inline and remote keysets and enforce the use of https for the remote set
    - Cover in unit tests
---
 .../op/profile/impl/AddJwksToClientMetadata.java   | 25 +++++++---
 .../profile/impl/AddJwksToClientMetadataTest.java  | 55 ++++++++++++++++++++++
 .../metadata/impl/public_keys_one_private.jwks     | 33 +++++++++++++
 3 files changed, 107 insertions(+), 6 deletions(-)

diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddJwksToClientMetadata.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddJwksToClientMetadata.java
index b6b38d7b..340a4932 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddJwksToClientMetadata.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddJwksToClientMetadata.java
@@ -123,24 +123,30 @@ public class AddJwksToClientMetadata extends AbstractOIDCClientMetadataPopulatio
         }
         
         if (jwkSet != null) {
-            if (containsKeys(jwkSet)) {
+            if (containsValidKeys(jwkSet)) {
                 getOutputMetadata().setJWKSet(jwkSet);
             } else {
-                log.warn("{} The jwks was defined, but it doesn't contain any keys", getLogPrefix());
+                log.warn("{} The jwks was defined, but it doesn't contain acceptable keys", getLogPrefix());
                 ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
             }
             return;                
         }
         
         if (jwkUri != null) {
+            if (!"https".equalsIgnoreCase(jwkUri.getScheme())) {
+                log.warn("{} The jwks_uri scheme was not https: {}", getLogPrefix(), jwkUri.getScheme());
+                ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+                return;
+            }
             if (validateRemoteJwkSetPredicate.test(profileRequestContext)) {
                 final JWKSet remoteSet = RemoteJwkUtils.fetchRemoteJwkSet(getLogPrefix(), jwkUri, httpClient, 
                         httpClientSecurityParameters);
-                if (containsKeys(remoteSet)) {
+                if (containsValidKeys(remoteSet)) {
                     log.debug("{} The jwks_uri endpoint available and contains key(s)", getLogPrefix());
                     getOutputMetadata().setJWKSetURI(jwkUri);
                 } else {
-                    log.warn("{} The jwks_uri was defined, but the endpoint does not contain key(s)", getLogPrefix());
+                    log.warn("{} The jwks_uri was defined, but the endpoint does not contain acceptable key(s)",
+                            getLogPrefix());
                     ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
                 }
             } else {
@@ -152,11 +158,11 @@ public class AddJwksToClientMetadata extends AbstractOIDCClientMetadataPopulatio
     }
     
     /**
-     * Checks that the given JWK set contains at least one key.
+     * Checks that the given JWK set contains at least one key and they are all public.
      * @param jwkSet The set of JWKs.
      * @return True if the set contains at least one key, false otherwise.
      */
-    protected boolean containsKeys(final JWKSet jwkSet) {
+    protected boolean containsValidKeys(@Nullable final JWKSet jwkSet) {
         if (jwkSet == null) {
             return false;
         }
@@ -164,6 +170,13 @@ public class AddJwksToClientMetadata extends AbstractOIDCClientMetadataPopulatio
         if (keys == null || keys.isEmpty()) {
             return false;
         }
+        for (final JWK key : keys) {
+            if (key.isPrivate()) {
+                log.warn("{} The keyset contains a private key {}, ignoring the keyset", getLogPrefix(),
+                        key.getKeyID());
+                return false;
+            }
+        }
         return true;
     }
 }
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddJwksToClientMetadataTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddJwksToClientMetadataTest.java
index 7190e86e..8d0b7e10 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddJwksToClientMetadataTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddJwksToClientMetadataTest.java
@@ -36,6 +36,7 @@ import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
+import com.nimbusds.jose.jwk.JWKSet;
 import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
 
 import net.shibboleth.idp.profile.testing.ActionTestingSupport;
@@ -130,4 +131,58 @@ public class AddJwksToClientMetadataTest extends BaseOIDCClientMetadataPopulatio
         Assert.assertEquals(result.getJWKSetURI(), MOCK_URI);
     }
 
+    @Test
+    public void testValidJwksWithPrivateKey() throws Exception {
+        String json = new String(Files.readAllBytes(Paths.get(getClass().getResource(
+                "/net/shibboleth/idp/oidc/metadata/impl/public_keys_one_private.jwks").toURI())));
+        action = constructWithProperties(json);
+        OIDCClientMetadata request = new OIDCClientMetadata();
+        request.setJWKSetURI(MOCK_URI);
+        OIDCClientMetadata result = new OIDCClientMetadata();
+        setUpContext(request, result);
+        Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertEvent(event, EventIds.INVALID_MESSAGE);
+    }
+
+    @Test
+    public void testValidJwksHttp() throws Exception {
+        String json = new String(Files.readAllBytes(Paths.get(getClass().getResource(
+                "/net/shibboleth/idp/oidc/metadata/impl/public_keys.jwks").toURI())));
+        action = constructWithProperties(json);
+        OIDCClientMetadata request = new OIDCClientMetadata();
+        final URI uri = URI.create("http://example.org");
+        request.setJWKSetURI(uri);
+        OIDCClientMetadata result = new OIDCClientMetadata();
+        setUpContext(request, result);
+        Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertEvent(event, EventIds.INVALID_MESSAGE);
+    }
+
+    @Test
+    public void testLocalValidJwks() throws Exception {
+        String json = new String(Files.readAllBytes(Paths.get(getClass().getResource(
+                "/net/shibboleth/idp/oidc/metadata/impl/public_keys.jwks").toURI())));
+        action = constructWithProperties(json);
+        OIDCClientMetadata request = new OIDCClientMetadata();
+        request.setJWKSet(JWKSet.parse(json));
+        OIDCClientMetadata result = new OIDCClientMetadata();
+        setUpContext(request, result);
+        Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertProceedEvent(event);
+        Assert.assertEquals(result.getJWKSet().size(), 2);
+    }
+
+    @Test
+    public void testLocalValidJwksWithPrivateKey() throws Exception {
+        String json = new String(Files.readAllBytes(Paths.get(getClass().getResource(
+                "/net/shibboleth/idp/oidc/metadata/impl/public_keys_one_private.jwks").toURI())));
+        action = constructWithProperties(json);
+        OIDCClientMetadata request = new OIDCClientMetadata();
+        request.setJWKSet(JWKSet.parse(json));
+        OIDCClientMetadata result = new OIDCClientMetadata();
+        setUpContext(request, result);
+        Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertEvent(event, EventIds.INVALID_MESSAGE);
+    }
+
 }
diff --git a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/oidc/metadata/impl/public_keys_one_private.jwks b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/oidc/metadata/impl/public_keys_one_private.jwks
new file mode 100644
index 00000000..7015109f
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/oidc/metadata/impl/public_keys_one_private.jwks
@@ -0,0 +1,33 @@
+{
+  "keys": [
+    {
+      "kid": "7978a91347261a291bd71dcab4a464be7d279666",
+      "e": "AQAB",
+      "kty": "RSA",
+      "alg": "RS256",
+      "n": "sFlU5LpHUtYIm7B27iiu7c4ZPZk7ULUNmFdMVsTmYJxJqQBKUIKU9ozwF6TlUsECmYUMLpQhX_iHuaZRcpG2YiG7jbmi9HMlonIXX7uUe7PIf8rNHhveX_VI7ZpwPTnab3_7ciy_o8ZFde6KNltkx_DLRO6hXf6z6ow1APFIIriaNlF8niz5cy0fPIv0e_Z2p13Sz3mnAACjBKZGPw2X9GWh5XpRoDEQBcibXpeLuA7ti8zLZuH-9ybXOoou699fr4QHFxUkcd_8fFqmzO5PKnlOnJZ0gtuXCCYYc9XPX-WSqlqbGNMZy0Giu2wHbNbeWdepkgVlGuJonTnMx4gLuQ",
+      "use": "sig"
+    },
+    {
+      "kid": "8aad66bdefc1b43d8db27e65e2e2ef301879d3e8",
+      "e": "AQAB",
+      "kty": "RSA",
+      "alg": "RS256",
+      "n": "vvAaaSpfr934Qx0ioFiWsopq7UCfLNn0zjYVbq4bvUcGSXU9kowYmQArR7WlIkjk1moffla0UV75QRaQPATva1oD5xQnnW-20haeMWTSsMgUHoN0Np9AD8ffPz-DfMJBOHIo4REL1BFFS33HSZgPl0hxJ-5UScqr4lW1JMy5XGeRho30dnmKTpakU1Oc35hFYKSea_O2SXfmbqiAkWlWkilEzgHq4pzVWiDZe4ZgfMdD4vqkSNrO_PkBFBT1mnBJztQ1h4v1jvUW-zeYYwIcPTaOX-xOTiGH9uQkcNPpe5pBrIZJqR5VNrDl_bJOmvVlhhXZSn4fkxA8kyQcZXGaTw",
+      "use": "sig"
+    },
+    {
+      "p": "1z63gO32_1Q2rxiQVhlw8aXBeyuDdOA2F0OSvPYozJQRcoRehJinWE0zkNS1fgoZYlfOSYgxbq00AEwuzW_nd1K9vY6GRCpgdDxemyK_qaq0mI6Ri5NY8cGYxtKdkHKvUscKotXaWYipTZfuGGegqT-YNrnIhSA9FIwQFOSxWLc",
+      "kty": "RSA",
+      "q": "zGiEZJVQSIqlAt_JrQ2AweO9oxb-OG9GjTEsXGkzX0ir66iuGDPokn4syntgAZaiDCQnN0gq7m7ueCunjXm1qMiGepWJ2KEoByxmicJy7wmZCNmCTEhMAxIx6xhdOtErrWRdtvqVObFbonxIdYeRAMkm622lxLGZjw99Ah2IUQE",
+      "d": "TZcMMYEgQVxl3E4vQfLgdQZjzr83m1bfZUHomkPx49RzqrvoqjpaxDCxFJhOu3fzHJRyUTaegyYFI3EzbLiYu68ywhlWsFIK2fuchick3JO859J2knI31-x8XHO0zGAyAjZyvpI83FV1Jm8mQg5Odk8UEx5kQEwXf22EfqnKNsQ-rbpq6OmNCOrf_mBjd7nf5OEkvisXeLgqCeEIAy2CDvLrdFMjCpF5OWPn_EiSDQ3qBCeZw6EmH5hz3vFbGGkqcaTJD_p6fDc3c1HFuMP7YBkpIsUYaYlfeysD-tOdEPLRdizzEdQbyxzErD0ac27fXQtC8VkkLs3oWSj9BIoiAQ",
+      "e": "AQAB",
+      "use": "sig",
+      "kid": "privateExample",
+      "qi": "CuHcWov_6EYK4OiKFii01OBpgP8FwsRTQBnO_spb2oF8OoTH5JKkcVkyHaUZRFm-n-pUpFPRKOBi42rWNCiuskdXH_JFepLSMkrcLarejvTw_jVg8KU2-G9AcI2fsxUMp8l0_1l3Scdws0Ff6XfWtAIWIyWvB5Ir2mS5tnZQkyU",
+      "dp": "qdeqMskTClxoHffzBI1kqrkRl-A5BYNaCzdd1U8j2HHJ5qP-dQCDo9CfLNw7Le5II2qHs0wNPXjcPJPgpmhzq5sVbzrERYtuuqlcOWvM8tNpV6u_uvQUzgbmR5Svkeh84YN_qAe5GMcWWMzWqlky0_oDYMNuL2XF7K2sjt_59Lc",
+      "dq": "t1CMWwAlTck5DwjPfyj67edmLJr0RnxU007kL8QQ1EUCGYffCcmTq70IQla-1O3YSfcqTEOmbMqDhrXvv8sFBWdAYzM0ulhc44IIO0dtd74txmf6mJrFwINSzTm-cn8gIpI-EVlM9r5-JYw8F1sKyZ-cDcr5vRphUpFbVMhTdgE",
+      "n": "q93bBjKqqkS-4Qzy7WPGX7QSxLJCxGFP0ek5vUJ-011u9eqN1QYBN093aAF7fpUjfP-DuwfhSCPbtPx3-WPERBOt848BfnHOoRzTYJWcD9JlqZdX6HjgkR8o8V6J8CPXfiXjgfWp_-Yf7TWgDgWYDgfBg4M56rNdDYzEGcT7B5k76QCHaWCCxZVm5R7aqxZZjKwdxyfjLdNhkHgzM62nSxwzw4d0WKiOuzBUh-xMaSg8mntE8xmBDMpJ8oOCHWI1qrEDPN2T2aAZsXGl-ijzQvPAOdl3eaUWF42rqSpmGwGaUBj5s2z8GKJkqYAh1dYjB39Wu0jakx4cEU4i3fs_tw"
+    }
+  ]
+}
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list