[java-plugin-shibd-oidc] branch dev/JSHIBDOIDC-31-cred-resolver updated: WIP: JSHIBDOIDC-34 - Add JWKS Storage Service Credential Resolver
Codeberg
noreply at shibboleth.net
Wed Oct 7 08:03:21 UTC 2026
This is an automated email from the git hooks/post-receive script.
codeberg pushed a commit to branch dev/JSHIBDOIDC-31-cred-resolver
in repository java-plugin-shibd-oidc.
View the commit online:
https://codeberg.org/Shibboleth/java-plugin-shibd-oidc/commit/0589de29deda302f95a49a4e62690eb1b73eebb8
The following commit(s) were added to refs/heads/dev/JSHIBDOIDC-31-cred-resolver by this push:
new 0589de2 WIP: JSHIBDOIDC-34 - Add JWKS Storage Service Credential Resolver
0589de2 is described below
commit 0589de29deda302f95a49a4e62690eb1b73eebb8
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Wed Oct 7 09:01:59 2026 +0100
WIP: JSHIBDOIDC-34 - Add JWKS Storage Service Credential Resolver
- Improve the JWKSet resolver to add a keyID criterion into the
velocity context if needed
- Removed the ability to set the kid to filter on by template. That is
now either based on the KeyID Criterion, or if null, no filtering. It
was to hard to determine when to return all keys if you could set
arbitrary kid filters, and I was not sure why you'd even do it. You can
still use the keyId in the key storage key template if you wanted e.g.
/oidc/{kid}/sp.jwks.
https://shibboleth.atlassian.net/browse/JSHIBDOIDC-34
---
.../sp/service/credentials/postconfig.xml | 1 +
.../JWKSetCredentialStorageServiceResolver.java | 103 ++++----
...izeOAuth2ClientAuthenticationMethodHandler.java | 3 +-
...JWKSetCredentialStorageServiceResolverTest.java | 281 +++++++++++++++++----
4 files changed, 296 insertions(+), 92 deletions(-)
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/credentials/postconfig.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/credentials/postconfig.xml
index fc10fab..00f8ea3 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/credentials/postconfig.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/credentials/postconfig.xml
@@ -12,6 +12,7 @@
default-init-method="initialize"
default-destroy-method="destroy">
+ <!-- Credential Resolvers that are auto-wired into the credential resolver service (shibboleth.sp.CredentialResolverService) -->
<!-- WIP: Additional resolver parent beans. -->
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/jose/credential/impl/JWKSetCredentialStorageServiceResolver.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/jose/credential/impl/JWKSetCredentialStorageServiceResolver.java
index 2e02887..b87d0aa 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/jose/credential/impl/JWKSetCredentialStorageServiceResolver.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/jose/credential/impl/JWKSetCredentialStorageServiceResolver.java
@@ -21,6 +21,7 @@ import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.exception.VelocityException;
@@ -35,6 +36,7 @@ import com.nimbusds.jose.jwk.JWKSet;
import net.shibboleth.oidc.security.credential.BasicJWKCredentialFactoryBean;
import net.shibboleth.oidc.security.credential.JWKCredential;
+import net.shibboleth.oidc.security.jose.criterion.KeyIdCriterion;
import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
import net.shibboleth.shared.annotation.constraint.NotEmpty;
import net.shibboleth.shared.annotation.constraint.NotLive;
@@ -50,37 +52,36 @@ import net.shibboleth.shared.velocity.Template;
import net.shibboleth.sp.credential.AbstractStorageServiceCredentialResolver;
/**
-* A specialized storage-based resolver that can resolve the {@link JWKCredential} type. Each storage record
+* A specialized storage-based resolver that resolves {@link JWKCredential}s. Each storage record
* represents a JWK Set.
*
-* <p>Individual JWKs are resolved by key ID ('kid'). While different keys within a JWK Set SHOULD use distinct 'kid'
-* values, uniqueness is not guaranteed, so multiple {@link JWKCredential}s may be resolved.</p>
-*
-* <p>JWKs are required to contain a 'kid' to be resolvable by this resolver.</p>
-*
-* <p>Does not currently deal with encrypted JWKs.</p>
+* <p>If a key ID is supplied in the criteria set, individual JWKs from the JWK Set are filtered by their {@code kid}.
+* Since key ID uniqueness within a JWK Set is not guaranteed, multiple {@link JWKCredential}s may
+* be resolved. If no key ID is supplied, all keys in the JWK Set are resolved.</p>
+*
+* <p>The key ID is made available to the storage key template as {@code ${kid}}, allowing the
+* storage location to be determined from the key ID when required. For example {@code "/oidc/${kid}/sp.jwks"}.</p>
+*
+* <p>Encrypted JWKs are not currently supported.</p>
*/
public class JWKSetCredentialStorageServiceResolver extends AbstractStorageServiceCredentialResolver<JWKCredential> {
+ /** The Velocity context variable name for the JWK Key ID. */
+ @Nonnull @NotEmpty public static final String CONTEXT_KEY_KID = "kid";
+
/** Class logger. */
@Nonnull private final Logger log = LoggerFactory.getLogger(JWKSetCredentialStorageServiceResolver.class);
/** Template used to construct the storage key for the JWK Set. */
@Nonnull private String jwkTemplateString;
- /** Template used to construct the Key ID. */
- @NonnullAfterInit private String kidTemplateString;
-
/** Velocity template for the JWK Set storage key. */
@NonnullAfterInit private Template jwkTemplate;
-
- /** Velocity template for the JWK Key ID. */
- @NonnullAfterInit private Template kidTemplate;
+ /** Constructor.*/
public JWKSetCredentialStorageServiceResolver() {
super(JWKCredential.class);
jwkTemplateString = "sp.jwks";
- kidTemplateString = "${agentID}-${usage}";
}
/**
@@ -97,21 +98,6 @@ public class JWKSetCredentialStorageServiceResolver extends AbstractStorageServi
"JSON Web Key template cannot be null or empty");
}
- /**
- * Set the Key ID Velocity template string for the key(s) to return from the KeySet.
- *
- * <p>Defaults to "${agentID}-${usage}", which assumes per-Agent entries
- * discriminated by usage.</p>
- *
- * @param template template string
- */
- public void setKidTemplateString(@Nonnull @NotEmpty final String template) {
- checkSetterPreconditions();
-
- kidTemplateString = Constraint.isNotNull(StringSupport.trimOrNull(template),
- "Key ID template cannot be null or empty");
- }
-
/** {@inheritDoc} */
@Override
protected void doInitialize() throws ComponentInitializationException {
@@ -119,8 +105,27 @@ public class JWKSetCredentialStorageServiceResolver extends AbstractStorageServi
jwkTemplate = Template.fromTemplate(getVelocityEngine(), jwkTemplateString,
StandardCharsets.UTF_8);
- kidTemplate = Template.fromTemplate(getVelocityEngine(), kidTemplateString,
- StandardCharsets.UTF_8);
+ }
+
+ /**
+ * {@inheritDoc}.
+ *
+ * <p>Add a Key ID ('kid') to the velocity context from a {@link KeyIdCriterion} for use.</p>
+ */
+ @Override
+ @Nonnull protected VelocityContext populateVelocityContext(@Nullable final CriteriaSet criteria) {
+ final VelocityContext ctx = super.populateVelocityContext(criteria);
+ if (criteria == null) {
+ return ctx;
+ }
+ final KeyIdCriterion keyIdCrit = criteria.get(KeyIdCriterion.class);
+ if (keyIdCrit != null) {
+ final String keyId = StringSupport.trimOrNull(keyIdCrit.getKeyId());
+ if (keyId != null) {
+ ctx.put(CONTEXT_KEY_KID, keyId);
+ }
+ }
+ return ctx;
}
/** {@inheritDoc} */
@@ -129,15 +134,13 @@ public class JWKSetCredentialStorageServiceResolver extends AbstractStorageServi
final CriteriaSet criteria, final String storageContext) throws ResolverException {
final StorageRecord<String> jwksData;
- final String kid;
+
+ final KeyIdCriterion keyIdCrit = criteria != null ? criteria.get(KeyIdCriterion.class) : null;
+ final String keyId = keyIdCrit != null ? StringSupport.trimOrNull(keyIdCrit.getKeyId()) : null;
- try {
-
- final String keyStorageKey = jwkTemplate.merge(velocityContext);
- kid = kidTemplate.merge(velocityContext);
-
- log.debug("CredentialResolver {}: Resolved storage keys for key ({}), kid ({})", getId(),
- keyStorageKey, kid);
+ try {
+ final String keyStorageKey = jwkTemplate.merge(velocityContext);
+ log.debug("CredentialResolver {}: Resolved storage key ({})", getId(), keyStorageKey);
jwksData = getStorageService().read(storageContext, keyStorageKey);
} catch (final VelocityException e) {
@@ -147,24 +150,28 @@ public class JWKSetCredentialStorageServiceResolver extends AbstractStorageServi
}
if (jwksData == null) {
- log.debug("Credential Resolver {}: No JWKS resolved", getId());
+ log.debug("Credential Resolver {}: No JWK Set resolved", getId());
return CollectionSupport.emptyList();
}
log.debug("CredentialResolver {}: Resolved data for JWK Set, constructing credentials", getId());
- // Load the entire JWKS to find the correct key
+ // Load the entire JWKS to find the correct key(s)
final JWKSet jwks;
try {
jwks = JWKSet.parse(jwksData.getValue());
} catch (final ParseException e) {
throw new ResolverException("Exception during creation of JWK Set", e);
- }
+ }
+
+ JWKSet matchingKeys = jwks;
- final JWKSet matchingKeys = jwks.filter(new JWKMatcher.Builder().keyID(kid).build());
- if (matchingKeys.isEmpty()) {
- log.debug("CredentialResolver {}: No JWK could be found with kid '{}'", getId(), kid);
- return CollectionSupport.emptyList();
+ if (keyId == null) {
+ // If no key ID was supplied, return all credentials
+ log.debug("CredentialResolver {}: No Key ID specified, all keys will be returned", getId());
+ } else {
+ // Else filter on kid
+ matchingKeys = jwks.filter(new JWKMatcher.Builder().keyID(keyId).build());
}
final List<JWKCredential> jwkCredentials = new ArrayList<>();
@@ -186,8 +193,8 @@ public class JWKSetCredentialStorageServiceResolver extends AbstractStorageServi
}
}
}
- log.debug("CredentialResolver {}: Resolved '{}' JWKs from JWK Set and kid '{}'",
- getId(), jwkCredentials.size(), kid);
+ log.debug("CredentialResolver {}: Resolved '{}' JWKs from JWK Set for Key ID '{}'",
+ getId(), jwkCredentials.size(), keyId == null ? "none" : keyId);
return CollectionSupport.copyToList(jwkCredentials);
}
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeOAuth2ClientAuthenticationMethodHandler.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeOAuth2ClientAuthenticationMethodHandler.java
index 21628f6..250f6c2 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeOAuth2ClientAuthenticationMethodHandler.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeOAuth2ClientAuthenticationMethodHandler.java
@@ -338,7 +338,8 @@ public class InitializeOAuth2ClientAuthenticationMethodHandler extends AbstractM
final ClientAuthenticationMethod method = new ClientAuthenticationMethod(clientAuthMethod);
ClientAuthentication clientAuthentication = null;
-
+ // If not a signed JWT, we just need the raw secret and do not need a populated security context to sign
+ // the JWT from.
if (method.equals(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) ||
method.equals(ClientAuthenticationMethod.CLIENT_SECRET_POST)) {
diff --git a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/credential/impl/JWKSetCredentialStorageServiceResolverTest.java b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/credential/impl/JWKSetCredentialStorageServiceResolverTest.java
index 3f4319e..00404eb 100644
--- a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/credential/impl/JWKSetCredentialStorageServiceResolverTest.java
+++ b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/credential/impl/JWKSetCredentialStorageServiceResolverTest.java
@@ -14,6 +14,7 @@
package net.shibboleth.sp.oidc.profile.credential.impl;
+import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;
import java.io.IOException;
@@ -34,6 +35,7 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.opensaml.core.criterion.EntityIdCriterion;
+import org.opensaml.security.credential.Credential;
import org.opensaml.security.credential.UsageType;
import org.opensaml.security.criteria.UsageCriterion;
import org.opensaml.security.crypto.KeySupport;
@@ -42,6 +44,7 @@ import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
+import com.google.common.collect.Iterables;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.KeyUse;
@@ -49,6 +52,7 @@ import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
import net.shibboleth.oidc.security.credential.JWKCredential;
+import net.shibboleth.oidc.security.jose.criterion.KeyIdCriterion;
import net.shibboleth.shared.collection.CollectionSupport;
import net.shibboleth.shared.collection.Pair;
import net.shibboleth.shared.resolver.CriteriaSet;
@@ -67,7 +71,7 @@ public class JWKSetCredentialStorageServiceResolverTest {
@BeforeMethod
public void setUp() throws IOException {
- testRoot = Files.createTempDirectory("test-jwk-resolver");
+ testRoot = Files.createTempDirectory("test-jwks-resolver");
}
private void tearDownWorker() throws IOException {
@@ -110,14 +114,14 @@ public class JWKSetCredentialStorageServiceResolverTest {
@Test
public void testStorageByAgent_TwoKeys() throws Exception {
- // Prep directory for agents.
- Files.createDirectories(Path.of(testRoot.toString(), "agents"));
+ // Prep directory for agent.
+ Files.createDirectories(Path.of(testRoot.toString(), "oidc"));
// Generate keys for testing.
- generateJWKS(Path.of(testRoot.toString(), "agents", "sp.jwks"),
+ generateJWKS(Path.of(testRoot.toString(), "oidc", "sp.jwks"),
CollectionSupport.listOf(
- new Pair<KeyUse,String>(KeyUse.SIGNATURE, "localhost-signing"),
- new Pair<KeyUse,String>(KeyUse.ENCRYPTION, "sp.example.org-encryption")));
+ new Pair<KeyUse,String>(KeyUse.SIGNATURE, "kid-signing"),
+ new Pair<KeyUse,String>(KeyUse.ENCRYPTION, "kid-encryption")));
final FilesystemStorageService storage = new FilesystemStorageService();
storage.setId("test");
@@ -129,12 +133,11 @@ public class JWKSetCredentialStorageServiceResolverTest {
resolver.setId("test");
resolver.setStorageService(storage);
resolver.setVelocityEngine(VelocityEngine.newVelocityEngine());
- resolver.setContextTemplate("agents");
- resolver.setKidTemplateString("$agentID-${usage}");
+ resolver.setContextTemplate("oidc");
resolver.initialize();
JWKCredential credential = (JWKCredential) resolver.resolveSingle(
- buildCriteriaSet("localhost", "default", "https://op.example.org/", UsageType.SIGNING));
+ buildCriteriaSet("localhost", "default", "https://op.example.org/", UsageType.SIGNING, "kid-signing"));
assert credential != null;
PrivateKey privKey = credential.getPrivateKey();
@@ -146,7 +149,8 @@ public class JWKSetCredentialStorageServiceResolverTest {
Assert.assertTrue(KeySupport.matchKeyPair(pubKey, privKey));
credential = (JWKCredential) resolver.resolveSingle(
- buildCriteriaSet("sp.example.org", "default", "https://op.example.org/", UsageType.ENCRYPTION));
+ buildCriteriaSet("localhost", "default", "https://op.example.org/", UsageType.ENCRYPTION,
+ "kid-encryption"));
assert credential != null;
privKey = credential.getPrivateKey();
@@ -158,21 +162,21 @@ public class JWKSetCredentialStorageServiceResolverTest {
Assert.assertTrue(KeySupport.matchKeyPair(pubKey, privKey));
credential = (JWKCredential) resolver.resolveSingle(
- buildCriteriaSet("missing-agent", "default", "https://op.example.org/", UsageType.ENCRYPTION));
+ buildCriteriaSet("localhost", "default", "https://op.example.org/", UsageType.ENCRYPTION, "wrong-kid"));
Assert.assertNull(credential);
}
@Test
- public void testStorageByAgent_OneKey() throws Exception {
+ public void testStorage_OneKey() throws Exception {
- // Prep directory for agents.
- Files.createDirectories(Path.of(testRoot.toString(), "agents"));
+ // Prep directory for agent.
+ Files.createDirectories(Path.of(testRoot.toString(), "oidc"));
// Generate keys for testing.
- generateJWKS(Path.of(testRoot.toString(), "agents", "sp.jwks"),
+ generateJWKS(Path.of(testRoot.toString(), "oidc", "sp.jwks"),
CollectionSupport.listOf(
- new Pair<KeyUse,String>(KeyUse.SIGNATURE, "localhost-signing")));
+ new Pair<KeyUse,String>(KeyUse.SIGNATURE, "kid-signing")));
final FilesystemStorageService storage = new FilesystemStorageService();
storage.setId("test");
@@ -184,12 +188,11 @@ public class JWKSetCredentialStorageServiceResolverTest {
resolver.setId("test");
resolver.setStorageService(storage);
resolver.setVelocityEngine(VelocityEngine.newVelocityEngine());
- resolver.setContextTemplate("agents");
- resolver.setKidTemplateString("$agentID-${usage}");
+ resolver.setContextTemplate("oidc");
resolver.initialize();
JWKCredential credential = (JWKCredential) resolver.resolveSingle(
- buildCriteriaSet("localhost", "default", "https://op.example.org/", UsageType.SIGNING));
+ buildCriteriaSet("localhost", "default", "https://op.example.org/", UsageType.SIGNING,"kid-signing"));
assert credential != null;
final PrivateKey privKey = credential.getPrivateKey();
@@ -201,23 +204,102 @@ public class JWKSetCredentialStorageServiceResolverTest {
Assert.assertTrue(KeySupport.matchKeyPair(pubKey, privKey));
credential = (JWKCredential) resolver.resolveSingle(
- buildCriteriaSet("sp.example.org", "default", "https://op.example.org/", UsageType.ENCRYPTION));
+ buildCriteriaSet("sp.localhost.org", "default", "https://op.example.org/", UsageType.ENCRYPTION,
+ "kid-encryption"));
assertNull(credential);
}
+ /* Should load any key if no KeyID specified.*/
@Test
- public void testStorageByAgent_TwoKeys_SameKid() throws Exception {
+ public void testStorage_OneKey_NoKid() throws Exception {
- // Prep directory for agents.
- Files.createDirectories(Path.of(testRoot.toString(), "agents"));
+ // Prep directory for agent.
+ Files.createDirectories(Path.of(testRoot.toString(), "oidc"));
+
+ // Generate keys for testing.
+ generateJWKS(Path.of(testRoot.toString(), "oidc", "sp.jwks"),
+ CollectionSupport.listOf(
+ new Pair<KeyUse,String>(KeyUse.SIGNATURE, "kid-signing")));
+
+ final FilesystemStorageService storage = new FilesystemStorageService();
+ storage.setId("test");
+ storage.setReadOnly(true);
+ storage.setStorageBase(testRoot.toString());
+ storage.initialize();
+
+ final JWKSetCredentialStorageServiceResolver resolver = new JWKSetCredentialStorageServiceResolver();
+ resolver.setId("test");
+ resolver.setStorageService(storage);
+ resolver.setVelocityEngine(VelocityEngine.newVelocityEngine());
+ resolver.setContextTemplate("oidc");
+ resolver.initialize();
+
+ JWKCredential credential = (JWKCredential) resolver.resolveSingle(
+ buildCriteriaSet("localhost", "default", "https://op.example.org/", UsageType.SIGNING,null));
+ assert credential != null;
+
+ final PrivateKey privKey = credential.getPrivateKey();
+ assert privKey != null;
+
+ final PublicKey pubKey = credential.getPublicKey();
+ assert pubKey != null;
+
+ Assert.assertTrue(KeySupport.matchKeyPair(pubKey, privKey));
+
+ credential = (JWKCredential) resolver.resolveSingle(
+ buildCriteriaSet("sp.localhost.org", "default", "https://op.example.org/", UsageType.ENCRYPTION,
+ "kid-encryption"));
+ assertNull(credential);
+
+ }
+
+ /* Use the kid for a signing key when encryption has been requested.*/
+ @Test
+ public void testStorage_WrongUsageForKid() throws Exception {
+
+ // Prep directory for agent.
+ Files.createDirectories(Path.of(testRoot.toString(), "oidc"));
+
+ // Generate keys for testing.
+ generateJWKS(Path.of(testRoot.toString(), "oidc", "sp.jwks"),
+ CollectionSupport.listOf(
+ new Pair<KeyUse,String>(KeyUse.SIGNATURE, "kid-signing"),
+ new Pair<KeyUse,String>(KeyUse.ENCRYPTION, "kid-encyrption")));
+
+ final FilesystemStorageService storage = new FilesystemStorageService();
+ storage.setId("test");
+ storage.setReadOnly(true);
+ storage.setStorageBase(testRoot.toString());
+ storage.initialize();
+
+ final JWKSetCredentialStorageServiceResolver resolver = new JWKSetCredentialStorageServiceResolver();
+ resolver.setId("test");
+ resolver.setStorageService(storage);
+ resolver.setVelocityEngine(VelocityEngine.newVelocityEngine());
+ resolver.setContextTemplate("oidc");
+ resolver.initialize();
+
+ final JWKCredential credential = (JWKCredential) resolver.resolveSingle(
+ buildCriteriaSet("localhost", "default", "https://op.example.org/", UsageType.ENCRYPTION,"kid-signing"));
+ assertNull(credential);
+
+ }
+
+
+ /* Same usage and same kid, which is allowable per spec.*/
+ @Test
+ public void testStorage_TwoKeys_SameKid_SameUsage() throws Exception {
+
+ // Prep directory for agent.
+ Files.createDirectories(Path.of(testRoot.toString(), "oidc"));
// Generate keys for testing. Same KID, which is allowed, both will be returned by the resolver, and then
// one from resolveSingle
- generateJWKS(Path.of(testRoot.toString(), "agents", "sp.jwks"),
+ generateJWKS(Path.of(testRoot.toString(), "oidc", "sp.jwks"),
CollectionSupport.listOf(
- new Pair<KeyUse,String>(KeyUse.SIGNATURE, "localhost-signing"),
- new Pair<KeyUse,String>(KeyUse.SIGNATURE, "localhost-signing")));
+ new Pair<KeyUse,String>(KeyUse.SIGNATURE, "kid-signing"),
+ new Pair<KeyUse,String>(KeyUse.SIGNATURE, "kid-signing")));
final FilesystemStorageService storage = new FilesystemStorageService();
storage.setId("test");
@@ -229,12 +311,11 @@ public class JWKSetCredentialStorageServiceResolverTest {
resolver.setId("test");
resolver.setStorageService(storage);
resolver.setVelocityEngine(VelocityEngine.newVelocityEngine());
- resolver.setContextTemplate("agents");
- resolver.setKidTemplateString("$agentID-${usage}");
+ resolver.setContextTemplate("oidc");
resolver.initialize();
final JWKCredential credential = (JWKCredential) resolver.resolveSingle(
- buildCriteriaSet("localhost", "default", "https://op.example.org/", UsageType.SIGNING));
+ buildCriteriaSet("localhost", "default", "https://op.example.org/", UsageType.SIGNING,"kid-signing"));
assert credential != null;
final PrivateKey privKey = credential.getPrivateKey();
@@ -244,21 +325,81 @@ public class JWKSetCredentialStorageServiceResolverTest {
assert pubKey != null;
Assert.assertTrue(KeySupport.matchKeyPair(pubKey, privKey));
+
+ final Iterable<Credential> credentials = resolver.resolve(
+ buildCriteriaSet("localhost", "default", "https://op.example.org/", UsageType.SIGNING,"kid-signing"));
+ assert credentials != null;
+
+ assertEquals(Iterables.size(credentials), 2);
}
+ /* Same usage but different kids.*/
@Test
- public void testCaching_TwoKeys() throws Exception {
+ public void testStorage_TwoKeys_DifferentKid_SameUsage() throws Exception {
+
+ // Prep directory for agent.
+ Files.createDirectories(Path.of(testRoot.toString(), "oidc"));
- // Prep directory for agents.
- Files.createDirectories(Path.of(testRoot.toString(), "agents"));
+ // Generate keys for testing. Same KID, which is allowed, both will be returned by the resolver, and then
+ // one from resolveSingle
+ generateJWKS(Path.of(testRoot.toString(), "oidc", "sp.jwks"),
+ CollectionSupport.listOf(
+ new Pair<KeyUse,String>(KeyUse.SIGNATURE, "kid-signing"),
+ new Pair<KeyUse,String>(KeyUse.SIGNATURE, "kid-signing-2")));
+
+ final FilesystemStorageService storage = new FilesystemStorageService();
+ storage.setId("test");
+ storage.setReadOnly(true);
+ storage.setStorageBase(testRoot.toString());
+ storage.initialize();
+
+ final JWKSetCredentialStorageServiceResolver resolver = new JWKSetCredentialStorageServiceResolver();
+ resolver.setId("test");
+ resolver.setStorageService(storage);
+ resolver.setVelocityEngine(VelocityEngine.newVelocityEngine());
+ resolver.setContextTemplate("oidc");
+ resolver.initialize();
+
+ JWKCredential credential = (JWKCredential) resolver.resolveSingle(
+ buildCriteriaSet("localhost", "default", "https://op.example.org/", UsageType.SIGNING,"kid-signing"));
+ assert credential != null;
+
+ PrivateKey privKey = credential.getPrivateKey();
+ assert privKey != null;
+
+ PublicKey pubKey = credential.getPublicKey();
+ assert pubKey != null;
+
+ Assert.assertTrue(KeySupport.matchKeyPair(pubKey, privKey));
+
+ credential = (JWKCredential) resolver.resolveSingle(
+ buildCriteriaSet("localhost", "default", "https://op.example.org/", UsageType.SIGNING,"kid-signing-2"));
+ assert credential != null;
+
+ privKey = credential.getPrivateKey();
+ assert privKey != null;
+
+ pubKey = credential.getPublicKey();
+ assert pubKey != null;
+
+ Assert.assertTrue(KeySupport.matchKeyPair(pubKey, privKey));
+
+
+ }
+
+ @Test
+ public void testCaching_TwoKeys_DifferentUsage() throws Exception {
+
+ // Prep directory for agent.
+ Files.createDirectories(Path.of(testRoot.toString(), "oidc"));
// Generate keys for testing.
- final Path jwksFile = Path.of(testRoot.toString(), "agents", "sp.jwks");
+ final Path jwksFile = Path.of(testRoot.toString(), "oidc", "sp.jwks");
generateJWKS(jwksFile,
CollectionSupport.listOf(
- new Pair<KeyUse,String>(KeyUse.SIGNATURE, "localhost-signing"),
- new Pair<KeyUse,String>(KeyUse.ENCRYPTION, "sp.example.org-encryption")));
+ new Pair<KeyUse,String>(KeyUse.SIGNATURE, "kid-signing"),
+ new Pair<KeyUse,String>(KeyUse.ENCRYPTION, "kid-encryption")));
final FilesystemStorageService storage = new FilesystemStorageService();
storage.setId("test");
@@ -270,14 +411,13 @@ public class JWKSetCredentialStorageServiceResolverTest {
resolver.setId("test");
resolver.setStorageService(storage);
resolver.setVelocityEngine(VelocityEngine.newVelocityEngine());
- resolver.setContextTemplate("agents");
- resolver.setKidTemplateString("$agentID-${usage}");
+ resolver.setContextTemplate("oidc");
resolver.setCacheEnabled(true);
- resolver.setExpireAfterAccess(Duration.ofSeconds(10));
+ resolver.setExpireAfterAccess(Duration.ofSeconds(4));
resolver.initialize();
JWKCredential credential = (JWKCredential) resolver.resolveSingle(
- buildCriteriaSet("localhost", "override", "https://op.example.org/", UsageType.SIGNING));
+ buildCriteriaSet("localhost", "override", "https://op.example.org/", UsageType.SIGNING, "kid-signing"));
assert credential != null;
final PrivateKey privKey = credential.getPrivateKey();
@@ -292,15 +432,65 @@ public class JWKSetCredentialStorageServiceResolverTest {
Files.delete(jwksFile);
credential = (JWKCredential) resolver.resolveSingle(
- buildCriteriaSet("localhost", "override", "https://op.example.org/", UsageType.SIGNING));
+ buildCriteriaSet("localhost", "override", "https://op.example.org/", UsageType.SIGNING, "kid-signing"));
Assert.assertNotNull(credential);
- Thread.sleep(15000);
+ Thread.sleep(7000);
credential = (JWKCredential) resolver.resolveSingle(
- buildCriteriaSet("localhost", "override", "https://op.example.org/", UsageType.SIGNING));
+ buildCriteriaSet("localhost", "override", "https://op.example.org/", UsageType.SIGNING, "kid-signing"));
Assert.assertNull(credential);
}
+
+ @Test
+ public void testCaching_NoKid_AllKeys() throws Exception {
+
+ // Prep directory for agent.
+ Files.createDirectories(Path.of(testRoot.toString(), "oidc"));
+
+ // Generate keys for testing.
+ final Path jwksFile = Path.of(testRoot.toString(), "oidc", "sp.jwks");
+ generateJWKS(jwksFile,
+ CollectionSupport.listOf(
+ new Pair<KeyUse,String>(KeyUse.SIGNATURE, "kid-signing"),
+ new Pair<KeyUse,String>(KeyUse.SIGNATURE, "kid-signing-2")));
+
+ final FilesystemStorageService storage = new FilesystemStorageService();
+ storage.setId("test");
+ storage.setReadOnly(true);
+ storage.setStorageBase(testRoot.toString());
+ storage.initialize();
+
+ final JWKSetCredentialStorageServiceResolver resolver = new JWKSetCredentialStorageServiceResolver();
+ resolver.setId("test");
+ resolver.setStorageService(storage);
+ resolver.setVelocityEngine(VelocityEngine.newVelocityEngine());
+ resolver.setContextTemplate("oidc");
+ resolver.setCacheEnabled(true);
+ resolver.setExpireAfterAccess(Duration.ofSeconds(4));
+ resolver.initialize();
+
+ Iterable<Credential> credentials = resolver.resolve(
+ buildCriteriaSet("localhost", "override", "https://op.example.org/", UsageType.SIGNING, null));
+ assert credentials != null;
+ assertEquals(Iterables.size(credentials), 2);
+
+ // Delete underlying file.
+ Files.delete(jwksFile);
+
+ credentials = resolver.resolve(
+ buildCriteriaSet("localhost", "override", "https://op.example.org/", UsageType.SIGNING, null));
+ assert credentials != null;
+ assertEquals(Iterables.size(credentials), 2);
+
+ // Sleep to expire
+ Thread.sleep(7000);
+ credentials = resolver.resolve(
+ buildCriteriaSet("localhost", "override", "https://op.example.org/", UsageType.SIGNING, null));
+ assert credentials != null;
+ assertEquals(Iterables.size(credentials), 0);
+
+ }
@@ -335,11 +525,12 @@ public class JWKSetCredentialStorageServiceResolverTest {
* @param applicationID application ID
* @param entityID entityID
* @param usage usage type
+ * @param keyId the key ID
*
* @return built criteria
*/
@Nonnull private CriteriaSet buildCriteriaSet(@Nullable final String agentID, @Nullable final String applicationID,
- @Nullable final String entityID, @Nullable final UsageType usage) {
+ @Nullable final String entityID, @Nullable final UsageType usage, @Nullable final String keyId) {
final CriteriaSet criteria = new CriteriaSet();
@@ -359,6 +550,10 @@ public class JWKSetCredentialStorageServiceResolverTest {
criteria.add(new UsageCriterion(usage));
}
+ if (keyId != null) {
+ criteria.add(new KeyIdCriterion(keyId));
+ }
+
return criteria;
}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list