[java-plugin-shibd] branch main updated: JSHIBD-25 - Develop necessary CredentialResolvers for SP service
Codeberg
noreply at shibboleth.net
Tue Aug 25 22:47:14 UTC 2026
This is an automated email from the git hooks/post-receive script.
codeberg pushed a commit to branch main
in repository java-plugin-shibd.
View the commit online:
https://codeberg.org/Shibboleth/java-plugin-shibd/commit/c882460726b7b0a28984723e5a7f5acdaa542355
The following commit(s) were added to refs/heads/main by this push:
new c882460 JSHIBD-25 - Develop necessary CredentialResolvers for SP service
c882460 is described below
commit c882460726b7b0a28984723e5a7f5acdaa542355
Author: Scott Cantor <scott at restingparrotsoftware.com>
AuthorDate: Tue Aug 25 18:47:01 2026 -0400
JSHIBD-25 - Develop necessary CredentialResolvers for SP service
https://shibboleth.atlassian.net/browse/JSHIBD-25
Initial unit test for X.509 resolver.
---
.../X509CredentialStorageServiceResolverTest.java | 223 +++++++++++++++++++++
1 file changed, 223 insertions(+)
diff --git a/sp-server-impl/src/test/java/net/shibboleth/sp/credential/impl/X509CredentialStorageServiceResolverTest.java b/sp-server-impl/src/test/java/net/shibboleth/sp/credential/impl/X509CredentialStorageServiceResolverTest.java
new file mode 100644
index 0000000..c39f6e4
--- /dev/null
+++ b/sp-server-impl/src/test/java/net/shibboleth/sp/credential/impl/X509CredentialStorageServiceResolverTest.java
@@ -0,0 +1,223 @@
+/*
+ * Licensed 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 net.shibboleth.sp.credential.impl;
+
+import java.io.IOException;
+import java.nio.file.DirectoryNotEmptyException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.core.criterion.EntityIdCriterion;
+import org.opensaml.security.credential.UsageType;
+import org.opensaml.security.criteria.UsageCriterion;
+import org.opensaml.security.crypto.KeySupport;
+import org.opensaml.security.x509.X509Credential;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.resolver.CriteriaSet;
+import net.shibboleth.shared.security.impl.SelfSignedCertificateGenerator;
+import net.shibboleth.shared.testing.VelocityEngine;
+import net.shibboleth.sp.AgentIDCriterion;
+import net.shibboleth.sp.ApplicationIDCriterion;
+import net.shibboleth.sp.storage.impl.FilesystemStorageService;
+
+/**
+ * Unit tests for {@link X509CredentialStorageServiceResolver}.
+ */
+ at SuppressWarnings("javadoc")
+public class X509CredentialStorageServiceResolverTest {
+
+ private Path testRoot;
+
+ @BeforeMethod
+ public void setUp() throws IOException {
+ testRoot = Files.createTempDirectory("test-x509-resolver");
+ }
+
+ private void tearDownWorker() throws IOException {
+ if (testRoot != null) {
+ Files.walkFileTree(testRoot, new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
+ throws IOException
+ {
+ Files.delete(file);
+ return FileVisitResult.CONTINUE;
+ }
+ @Override
+ public FileVisitResult postVisitDirectory(Path dir, IOException e)
+ throws IOException
+ {
+ if (e == null) {
+ Files.delete(dir);
+ return FileVisitResult.CONTINUE;
+ }
+ // directory iteration failed
+ throw e;
+ }
+ });
+ testRoot = null;
+ }
+ }
+
+ @AfterMethod
+ public void tearDown() throws IOException, InterruptedException {
+ try {
+ tearDownWorker();
+ } catch (final DirectoryNotEmptyException ex) {
+ // We hates the Microsoft Defender. (it pins files so directories cannot be deleted)
+ Thread.sleep(10);
+ tearDownWorker();
+ }
+ }
+
+ @Test
+ public void testStorageByAgent() throws Exception {
+
+ // Prep directories for agents.
+ Files.createDirectories(Path.of(testRoot.toString(), "agents", "localhost"));
+ Files.createDirectories(Path.of(testRoot.toString(), "agents", "sp.example.org"));
+
+ // Generate keypairs for testing.
+ generateKeyPair(
+ Path.of(testRoot.toString(), "agents", "localhost", "sp-signing.key"),
+ Path.of(testRoot.toString(), "agents", "localhost", "sp-signing.crt"),
+ "localhost", null);
+ generateKeyPair(
+ Path.of(testRoot.toString(), "agents", "sp.example.org", "sp-encryption.key"),
+ Path.of(testRoot.toString(), "agents", "sp.example.org", "sp-encryption.crt"),
+ "sp.example.org", "https://sp.example.org/sp");
+
+ final FilesystemStorageService storage = new FilesystemStorageService();
+ storage.setId("test");
+ storage.setReadOnly(true);
+ storage.setStorageBase(testRoot.toString());
+ storage.initialize();
+
+ final X509CredentialStorageServiceResolver resolver = new X509CredentialStorageServiceResolver();
+ resolver.setId("test");
+ resolver.setStorageService(storage);
+ resolver.setVelocityEngine(VelocityEngine.newVelocityEngine());
+ resolver.setContextTemplate("agents/$agentID");
+ resolver.initialize();
+
+ X509Credential credential = (X509Credential) resolver.resolveSingle(
+ buildCriteriaSet("localhost", "default", "https://idp.example.org/idp", UsageType.SIGNING));
+ assert credential != null;
+
+ PrivateKey key = credential.getPrivateKey();
+ assert key != null;
+
+ X509Certificate cert = credential.getEntityCertificate();
+ assert cert != null;
+
+ Assert.assertTrue(KeySupport.matchKeyPair(cert.getPublicKey(), key));
+ Assert.assertEquals(cert.getSubjectAlternativeNames(), CollectionSupport.singletonList(
+ CollectionSupport.listOf(Integer.valueOf(2), "localhost")));
+
+ Assert.assertNull(credential.getCRLs());
+
+ credential = (X509Credential) resolver.resolveSingle(
+ buildCriteriaSet("sp.example.org", "default", "https://idp.example.org/idp", UsageType.ENCRYPTION));
+ assert credential != null;
+
+ key = credential.getPrivateKey();
+ assert key != null;
+
+ cert = credential.getEntityCertificate();
+ assert cert != null;
+
+ Assert.assertTrue(KeySupport.matchKeyPair(cert.getPublicKey(), key));
+ Assert.assertEqualsNoOrder(cert.getSubjectAlternativeNames(), CollectionSupport.listOf(
+ CollectionSupport.listOf(Integer.valueOf(2), "sp.example.org"),
+ CollectionSupport.listOf(Integer.valueOf(6), "https://sp.example.org/sp")));
+
+ Assert.assertNull(credential.getCRLs());
+
+ credential = (X509Credential) resolver.resolveSingle(
+ buildCriteriaSet("missing-agent", "default", "https://idp.example.org/idp", UsageType.ENCRYPTION));
+ Assert.assertNull(credential);
+ }
+
+ /**
+ * Generate a self-signed keypair.
+ *
+ * @param key private key file
+ * @param cert certificate file
+ * @param hostname hostname for subject and DNS sAN
+ * @param entityID URI for URI sAN
+ *
+ * @throws Exception on errors
+ */
+ private void generateKeyPair(@Nonnull final Path key, @Nonnull final Path cert, @Nonnull final String hostname,
+ @Nullable final String entityID) throws Exception {
+ final SelfSignedCertificateGenerator generator = new SelfSignedCertificateGenerator();
+ generator.setKeySize(1024);
+ generator.setHostName(hostname);
+ if (entityID != null) {
+ generator.setURISubjectAltNames(CollectionSupport.singletonList(entityID));
+ }
+ generator.setPrivateKeyFile(key.toFile());
+ generator.setCertificateFile(cert.toFile());
+ generator.generate();
+ }
+
+ /**
+ * Builds a set of criteria for the resolver.
+ *
+ * @param agentID agent ID
+ * @param applicationID application ID
+ * @param entityID entityID
+ * @param usage usage type
+ *
+ * @return built criteria
+ */
+ @Nonnull private CriteriaSet buildCriteriaSet(@Nullable final String agentID, @Nullable final String applicationID,
+ @Nullable final String entityID, @Nullable final UsageType usage) {
+
+ final CriteriaSet criteria = new CriteriaSet();
+
+ if (agentID != null) {
+ criteria.add(new AgentIDCriterion(agentID));
+ }
+
+ if (applicationID != null) {
+ criteria.add(new ApplicationIDCriterion(applicationID));
+ }
+
+ if (entityID != null) {
+ criteria.add(new EntityIdCriterion(entityID));
+ }
+
+ if (usage != null) {
+ criteria.add(new UsageCriterion(usage));
+ }
+
+ return criteria;
+ }
+
+}
\ 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