[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