[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