[java-plugin-shibd] branch main updated: JSHIBD-25 - Develop necessary CredentialResolvers for SP service

Codeberg noreply at shibboleth.net
Tue Aug 25 15:49:41 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/d4979c733d037cd63056c41ccb5e1ca87c0f4d07

The following commit(s) were added to refs/heads/main by this push:
     new d4979c7  JSHIBD-25 - Develop necessary CredentialResolvers for SP service
d4979c7 is described below

commit d4979c733d037cd63056c41ccb5e1ca87c0f4d07
Author: Scott Cantor <scott at restingparrotsoftware.com>
AuthorDate: Tue Aug 25 11:49:26 2026 -0400

    JSHIBD-25 - Develop necessary CredentialResolvers for SP service
    
    https://shibboleth.atlassian.net/browse/JSHIBD-25
    
    Adjustments to resolver class responsibilities for Velocity templates.
    Fix factory bean usage.
---
 .../AbstractStorageServiceCredentialResolver.java  | 71 ++++++++++------
 .../impl/X509CredentialStorageServiceResolver.java | 96 +++++++++++++++-------
 2 files changed, 111 insertions(+), 56 deletions(-)

diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/credential/AbstractStorageServiceCredentialResolver.java b/sp-server-api/src/main/java/net/shibboleth/sp/credential/AbstractStorageServiceCredentialResolver.java
index 0845e58..be05604 100644
--- a/sp-server-api/src/main/java/net/shibboleth/sp/credential/AbstractStorageServiceCredentialResolver.java
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/credential/AbstractStorageServiceCredentialResolver.java
@@ -16,6 +16,7 @@ package net.shibboleth.sp.credential;
 
 import java.nio.charset.StandardCharsets;
 import java.security.NoSuchAlgorithmException;
+import java.util.Map;
 import java.util.function.Function;
 
 import javax.annotation.Nonnull;
@@ -23,6 +24,7 @@ import javax.annotation.Nullable;
 
 import org.apache.velocity.VelocityContext;
 import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.exception.VelocityException;
 import org.opensaml.core.criterion.EntityIdCriterion;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.opensaml.profile.criterion.ProfileRequestContextCriterion;
@@ -135,14 +137,11 @@ public abstract class AbstractStorageServiceCredentialResolver<T extends Credent
     /** Velocity template source for context. */
     @NonnullAfterInit private String contextTemplateString;
 
-    /** Velocity template source for key. */
-    @NonnullAfterInit private String keyTemplateString;
-
     /** Velocity template for context. */
     @NonnullAfterInit private Template contextTemplate;
-
-    /** Velocity template for key. */
-    @NonnullAfterInit private Template keyTemplate;
+    
+    /** Map of usage types to template values. */
+    @Nonnull private Map<UsageType,String> usageMap;
     
     /**
      * Constructor.
@@ -151,7 +150,8 @@ public abstract class AbstractStorageServiceCredentialResolver<T extends Credent
      */
     public AbstractStorageServiceCredentialResolver(@Nonnull @ParameterName(name="type") final Class<T> type) {
         credentialType = Constraint.isNotNull(type, "Credential type cannot be null");
-        
+        usageMap = Map.of(UsageType.SIGNING, "-signing", UsageType.ENCRYPTION, "-encryption",
+                UsageType.UNSPECIFIED, "-signing");
     }
     
     /**
@@ -174,6 +174,15 @@ public abstract class AbstractStorageServiceCredentialResolver<T extends Credent
         storageService = Constraint.isNotNull(storage, "StorageService cannot be null");
     }
     
+    /**
+     * Gets the {@link VelocityEngine} to use.
+     * 
+     * @return Velocity engine
+     */
+    @NonnullAfterInit public VelocityEngine getVelocityEngine() {
+        return velocityEngine;
+    }
+    
     /**
      * Sets the {@link VelocityEngine} to use.
      * 
@@ -210,16 +219,24 @@ public abstract class AbstractStorageServiceCredentialResolver<T extends Credent
         
         contextTemplateString = Constraint.isNotNull(template, "Storage context template cannot be null");
     }
-
+    
     /**
-     * Sets the Velocity template to use to construct the storage key.
+     * Sets a map from {@link UsageType} to string token, allowing customizable injection of a string
+     * into the Velocity context to abbreviate or otherwise control what to resolve based on a supplied
+     * {@link UsageCriterion}.
+     * 
+     * <p>Defaults to "-signing", "-encryption", and "-signing" for {@link UsageType#SIGNING},
+     * {@link UsageType#ENCRYPTION}, and {@link UsageType#UNSPECIFIED} respectively, which is
+     * compatible with the advisable step of separating keys by usage.</p>
      * 
-     * @param template template for storage key
+     * @param map map of usage types to string values
      */
-    public void setKeyTemplate(@Nonnull final String template) {
-        checkSetterPreconditions();
-        
-        contextTemplateString = Constraint.isNotNull(template, "Storage key template cannot be null");
+    public void setUsageMap(@Nullable final Map<UsageType,String> map) {
+        if (map != null) {
+            usageMap = CollectionSupport.copyToMap(map);
+        } else {
+            usageMap = CollectionSupport.emptyMap();
+        }
     }
     
     /** {@inheritDoc} */
@@ -231,12 +248,8 @@ public abstract class AbstractStorageServiceCredentialResolver<T extends Credent
             throw new ComponentInitializationException("StorageService cannot be null");
         } else if (velocityEngine == null) {
             throw new ComponentInitializationException("VelocityEngine cannot be null");
-        } else if (keyTemplateString == null) {
-            throw new ComponentInitializationException("Storage key template cannot be null");
         }
         
-        keyTemplate = Template.fromTemplate(velocityEngine, keyTemplateString, StandardCharsets.UTF_8);
-        
         if (contextTemplateString == null) {
             contextTemplateString = DEFAULT_CONTEXT_PREFIX + ensureId();
         }
@@ -265,12 +278,16 @@ public abstract class AbstractStorageServiceCredentialResolver<T extends Credent
         
         final VelocityContext ctx = populateVelocityContext(criteria);
         
-        final String storageContext = contextTemplate.merge(ctx);
-        final String storageKey = keyTemplate.merge(ctx);
+        final String storageContext;
+        try {
+            storageContext = contextTemplate.merge(ctx);
+        } catch (final VelocityException e) {
+            throw new ResolverException("Error executing context template", e);
+        }
         
-        log.debug("{}: Resolved storage context ({}), key ({})", getId(), storageContext, storageKey);
+        log.debug("{}: Resolved storage context ({})", getId(), storageContext);
         
-        return doResolve(criteria, storageContext, storageKey);
+        return doResolve(ctx, criteria, storageContext);
     }
     
     /**
@@ -312,9 +329,9 @@ public abstract class AbstractStorageServiceCredentialResolver<T extends Credent
         
         final UsageCriterion usageCrit = criteria.get(UsageCriterion.class);
         if (usageCrit != null) {
-            ctx.put(CONTEXT_KEY_USAGE, usageCrit.getUsage());
+            ctx.put(CONTEXT_KEY_USAGE, usageMap.get(usageCrit.getUsage()));
         } else {
-            ctx.put(CONTEXT_KEY_USAGE, UsageType.UNSPECIFIED);
+            ctx.put(CONTEXT_KEY_USAGE, usageMap.get(UsageType.UNSPECIFIED));
         }
         
         return ctx;
@@ -330,17 +347,17 @@ public abstract class AbstractStorageServiceCredentialResolver<T extends Credent
      * the information needed for multiple objects without some fairly exotic tricks, but this is left
      * to the subclass to decide.</p>
      * 
+     * @param velocityContext Velocity context constructed from criteria 
      * @param criteria input to resolver
      * @param storageContext evaluated storage context
-     * @param storageKey evaluated storage key
      * 
      * @return zero or more resolved credentials
      * 
      * @throws ResolverException on errors, generally wrapped from storage service 
      */
     @Nonnull @NotLive @Unmodifiable protected abstract Iterable<Credential> doResolve(
-            @Nullable final CriteriaSet criteria, @Nonnull final String storageContext,
-            @Nonnull final String storageKey) throws ResolverException;
+            @Nonnull final VelocityContext velocityContext, @Nullable final CriteriaSet criteria,
+            @Nonnull final String storageContext) throws ResolverException;
 
     /**
      * Defaults to SHA-1 hash.
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/credential/impl/X509CredentialStorageServiceResolver.java b/sp-server-impl/src/main/java/net/shibboleth/sp/credential/impl/X509CredentialStorageServiceResolver.java
index ae86610..b89deea 100644
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/credential/impl/X509CredentialStorageServiceResolver.java
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/credential/impl/X509CredentialStorageServiceResolver.java
@@ -20,6 +20,8 @@ import java.nio.charset.StandardCharsets;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.exception.VelocityException;
 import org.opensaml.security.credential.Credential;
 import org.opensaml.security.x509.X509Credential;
 import org.opensaml.spring.credential.BasicX509CredentialFactoryBean;
@@ -27,16 +29,19 @@ import org.opensaml.storage.StorageRecord;
 import org.slf4j.Logger;
 import org.springframework.core.io.ByteArrayResource;
 
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.annotation.constraint.NotLive;
 import net.shibboleth.shared.annotation.constraint.Unmodifiable;
 import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
 import net.shibboleth.shared.primitive.StringSupport;
 import net.shibboleth.shared.resolver.CriteriaSet;
 import net.shibboleth.shared.resolver.ResolverException;
 import net.shibboleth.shared.spring.resource.ResourceHelper;
+import net.shibboleth.shared.velocity.Template;
 import net.shibboleth.sp.credential.AbstractStorageServiceCredentialResolver;
 
 /**
@@ -55,16 +60,25 @@ public class X509CredentialStorageServiceResolver extends AbstractStorageService
     @Nonnull private final Logger log = LoggerFactory.getLogger(X509CredentialStorageServiceResolver.class);
     
     /** Suffix added to storage key when searching for the private key. */
-    @Nonnull private String privateKeySuffix;
+    @Nonnull private String privateKeyTemplateString;
 
     /** Suffix added to storage key when searching for the public key certificate(s). */
-    @Nonnull private String certificateSuffix;
+    @Nonnull private String certificateTemplateString;
 
     /** Suffix added to storage key when searching for the CRL(s). */
-    @Nullable private String crlSuffix;
+    @Nullable private String crlTemplateString;
     
     /** Password for private key(s). */
     @Nullable private char[] privateKeyPassword;
+
+    /** Velocity template for private key storage key. */
+    @NonnullAfterInit Template privateKeyTemplate;
+        
+    /** Velocity template for certificate storage key. */
+    @NonnullAfterInit Template certificateTemplate;
+    
+    /** Velocity template for CRL storage key. */
+    @NonnullAfterInit Template crlTemplate;
     
     /**
      * Constructor.
@@ -72,49 +86,49 @@ public class X509CredentialStorageServiceResolver extends AbstractStorageService
     public X509CredentialStorageServiceResolver() {
         super(X509Credential.class);
         
-        privateKeySuffix = ".key";
-        certificateSuffix = ".crt";
+        privateKeyTemplateString = "sp${usage}.key";
+        certificateTemplateString = "sp${usage}.crt";
     }
 
     /**
-     * Sets the storage key suffix to add when resolving the private key.
+     * Sets the Velocity template string for the private key's storage key.
      * 
-     * <p>Defaults to ".key"</p>
+     * <p>Defaults to "sp$usage.key", which punts any other decoration to the storage context.</p>
      * 
-     * @param suffix suffix to add
+     * @param template template string
      */
-    public void setPrivateKeySuffix(@Nonnull @NotEmpty final String suffix) {
+    public void setPrivateKeyTemplate(@Nonnull @NotEmpty final String template) {
         checkSetterPreconditions();
         
-        privateKeySuffix = Constraint.isNotNull(StringSupport.trimOrNull(suffix),
-                "Private key suffix cannot be null or empty");
+        privateKeyTemplateString = Constraint.isNotNull(StringSupport.trimOrNull(template),
+                "Private key template cannot be null or empty");
     }
     
     /**
-     * Sets the storage key suffix to add when resolving the certificate(s).
+     * Sets the Velocity template string for the certificate's storage key.
      * 
-     * <p>Defaults to ".crt"</p>
+     * <p>Defaults to "sp$usage.crt", which punts any other decoration to the storage context.</p>
      * 
-     * @param suffix suffix to add
+     * @param template template string
      */
-    public void setCertificateSuffix(@Nonnull @NotEmpty final String suffix) {
+    public void setCertificateTemplate(@Nonnull @NotEmpty final String template) {
         checkSetterPreconditions();
         
-        certificateSuffix = Constraint.isNotNull(StringSupport.trimOrNull(suffix),
-                "Certificate suffix cannot be null or empty");
+        certificateTemplateString = Constraint.isNotNull(StringSupport.trimOrNull(template),
+                "Certificate template cannot be null or empty");
     }
 
     /**
-     * Sets the storage key suffix to add when resolving the CRL(s).
+     * Sets the Velocity template string for the CRL's storage key.
      * 
-     * <p>Defaults to null, which skips attempted resolution of CRLs.</p>
+     * <p>Defaults to null, ignoring the CRL component.</p>
      * 
-     * @param suffix suffix to add
+     * @param template template string
      */
-    public void setCRLSuffix(@Nullable @NotEmpty final String suffix) {
+    public void setCRLTemplateString(@Nullable @NotEmpty final String template) {
         checkSetterPreconditions();
         
-        crlSuffix = StringSupport.trimOrNull(suffix);
+        crlTemplateString = StringSupport.trimOrNull(template);
     }
     
     /**
@@ -128,26 +142,47 @@ public class X509CredentialStorageServiceResolver extends AbstractStorageService
         privateKeyPassword = password != null ? password.toCharArray() : null;
     }
 
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        
+        privateKeyTemplate = Template.fromTemplate(getVelocityEngine(), privateKeyTemplateString,
+                StandardCharsets.UTF_8);
+        certificateTemplate = Template.fromTemplate(getVelocityEngine(), certificateTemplateString,
+                StandardCharsets.UTF_8);
+        final String local = crlTemplateString;
+        if (local != null) {
+            crlTemplate = Template.fromTemplate(getVelocityEngine(), local, StandardCharsets.UTF_8);
+        }
+    }
+    
     /** {@inheritDoc} 
      * @throws ResolverException */
     @Override
-    @Nonnull @NotLive @Unmodifiable protected Iterable<Credential> doResolve(
-            @Nullable final CriteriaSet criteria, @Nonnull final String storageContext,
-            @Nonnull final String storageKey) throws ResolverException {
+    @Nonnull @NotLive @Unmodifiable protected Iterable<Credential> doResolve(@Nonnull VelocityContext velocityContext,
+            @Nullable final CriteriaSet criteria, @Nonnull final String storageContext) throws ResolverException {
  
         final StorageRecord<String> privateKeyData;
         final StorageRecord<String> certificateData;
         final StorageRecord<String> crlData;
 
         try {
-            privateKeyData = getStorageService().read(storageContext, storageKey + privateKeySuffix);
-            certificateData = getStorageService().read(storageContext, storageKey + certificateSuffix);
-            if (crlSuffix != null) {
-                crlData = getStorageService().read(storageContext, storageKey + crlSuffix);
+            final String keyStorage = privateKeyTemplate.merge(velocityContext);
+            final String certStorage = certificateTemplate.merge(velocityContext);
+
+            log.debug("{}: Resolved storage keys for key ({}), certificate ({})", getId(), keyStorage, certStorage);
+            
+            privateKeyData = getStorageService().read(storageContext, keyStorage);
+            certificateData = getStorageService().read(storageContext, certStorage);
+            if (crlTemplate != null) {
+                crlData = getStorageService().read(storageContext, crlTemplate.merge(velocityContext));
             } else {
                 crlData = null;
             }
             
+        } catch (final VelocityException e) {
+            throw new ResolverException("Error executing storage key template", e);
         } catch (final IOException e) {
             throw new ResolverException("I/O exception retrieving credential data", e);
         }
@@ -157,6 +192,8 @@ public class X509CredentialStorageServiceResolver extends AbstractStorageService
             return CollectionSupport.emptyList();
         }
         
+        log.debug("{}: Resolved data for key and/or certificate, constructing credential", getId());
+        
         // Populate a factory bean from the resolved material.
         
         final BasicX509CredentialFactoryBean factory = new BasicX509CredentialFactoryBean();
@@ -179,6 +216,7 @@ public class X509CredentialStorageServiceResolver extends AbstractStorageService
         }
         
         try {
+            factory.afterPropertiesSet();
             final X509Credential credential = factory.getObject();
             if (credential != null) {
                 log.debug("{}: Resolved X509Credential for caller", getId());

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


More information about the commits mailing list