[java-idp-plugin-vci] branch main updated: Rework signing configurations. Credentials and Status List signing have their own Signing Configurations.

Codeberg noreply at shibboleth.net
Thu Aug 27 10:19:27 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-plugin-vci.

View the commit online:
https://codeberg.org/Shibboleth/java-idp-plugin-vci/commit/54ff39d0109bf3043b8410c20f65494e5c3b72c4

The following commit(s) were added to refs/heads/main by this push:
     new 54ff39d  Rework signing configurations. Credentials and Status List signing have their own Signing Configurations.
54ff39d is described below

commit 54ff39d0109bf3043b8410c20f65494e5c3b72c4
Author: Janne Lauros <janne.lauros at csc.fi>
AuthorDate: Thu Aug 27 13:07:17 2026 +0300

    Rework signing configurations. Credentials and Status List signing have their own Signing Configurations.
---
 .../config/DefaultStatusListConfiguration.java     |  59 +++++++++
 ...ignatureSigningConfigurationLookupFunction.java |  64 +++++++++
 .../openidvci/config/OpenIDVCIConfiguration.java   |  18 +++
 .../impl/AbstractOpenIDVCIConfiguration.java       |  40 +++++-
 ...ignatureSigningConfigurationLookupFunction.java | 145 +++++++++++++++++++++
 ...thmRestrictedSignatureSigningConfiguration.java | 131 +++++++++++++++++++
 .../flows/oauth/status-list/list/list-beans.xml    |  18 +--
 .../openid/vci/credentials/credentials-beans.xml   |  17 ++-
 .../idp/service/relying-party/postconfig.xml       |  39 +++++-
 .../openidvci/conf/openid-vci-credentials.xml      |  93 +++++++++++++
 .../plugin/openidvci/conf/openid-vci.properties    |   5 +
 .../shibboleth/plugin/openidvci/module.properties  |   7 +
 12 files changed, 613 insertions(+), 23 deletions(-)

diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/oauth/statuslist/config/DefaultStatusListConfiguration.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/oauth/statuslist/config/DefaultStatusListConfiguration.java
index 589edcb..0ed6b15 100644
--- a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/oauth/statuslist/config/DefaultStatusListConfiguration.java
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/oauth/statuslist/config/DefaultStatusListConfiguration.java
@@ -16,10 +16,19 @@
 
 package org.geant.shibboleth.plugin.oauth.statuslist.config;
 
+import java.util.function.Function;
+
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
 
 import net.shibboleth.idp.profile.config.AbstractInterceptorAwareProfileConfiguration;
+import net.shibboleth.oidc.security.jose.SignatureSigningConfiguration;
+import net.shibboleth.shared.annotation.ConfigurationSetting;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.FunctionSupport;
 
 /**
  * Profile configuration for the endpoint publishing Status List Tokens, as in
@@ -38,9 +47,59 @@ public class DefaultStatusListConfiguration extends AbstractInterceptorAwareProf
     @NotEmpty
     public static final String PROFILE_ID = "http://geant.org/ns/profiles/oauth/status-list";
 
+    /** Signing of Status List Tokens. */
+    @Nonnull
+    private Function<ProfileRequestContext, SignatureSigningConfiguration> statusListSignatureSigningConfigurationLookupStrategy;
+
     /** Constructor. */
     public DefaultStatusListConfiguration() {
         super(PROFILE_ID);
+        statusListSignatureSigningConfigurationLookupStrategy = FunctionSupport.constant(null);
+    }
+
+    /**
+     * Get the {@link SignatureSigningConfiguration} used for signing Status List
+     * Tokens.
+     *
+     * <p>
+     * Status List Token names same issuer as tokens referring to it. While issuer is
+     * derived from signing key, like did:jwk is, this must resolve to same key those
+     * tokens are signed with.
+     * </p>
+     *
+     * @param profileRequestContext current profile request context
+     *
+     * @return the signature signing configuration to use
+     */
+    @ConfigurationSetting(name="statusListSignatureSigningConfiguration")
+    @Nullable
+    public SignatureSigningConfiguration getStatusListSignatureSigningConfiguration(
+            @Nullable final ProfileRequestContext profileRequestContext) {
+        return statusListSignatureSigningConfigurationLookupStrategy.apply(profileRequestContext);
+    }
+
+    /**
+     * Set the {@link SignatureSigningConfiguration} to sign Status List Tokens with.
+     *
+     * @param configuration configuration to use
+     *
+     */
+    public void setStatusListSignatureSigningConfiguration(
+            @Nullable final SignatureSigningConfiguration configuration) {
+        statusListSignatureSigningConfigurationLookupStrategy = FunctionSupport.constant(configuration);
+    }
+
+    /**
+     * Set a lookup strategy for the {@link SignatureSigningConfiguration} to sign
+     * Status List Tokens with.
+     *
+     * @param strategy lookup strategy
+     *
+     */
+    public void setStatusListSignatureSigningConfigurationLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, SignatureSigningConfiguration> strategy) {
+        statusListSignatureSigningConfigurationLookupStrategy = Constraint.isNotNull(strategy,
+                "Lookup strategy cannot be null");
     }
 
 }
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/oauth/statuslist/config/navigate/StatusListSignatureSigningConfigurationLookupFunction.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/oauth/statuslist/config/navigate/StatusListSignatureSigningConfigurationLookupFunction.java
new file mode 100644
index 0000000..b1b9d76
--- /dev/null
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/oauth/statuslist/config/navigate/StatusListSignatureSigningConfigurationLookupFunction.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2025, GÉANT
+ *
+ * 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 org.geant.shibboleth.plugin.oauth.statuslist.config.navigate;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.geant.shibboleth.plugin.oauth.statuslist.config.DefaultStatusListConfiguration;
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.oidc.security.jose.SignatureSigningConfiguration;
+import net.shibboleth.profile.config.ProfileConfiguration;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.profile.context.navigate.AbstractRelyingPartyLookupFunction;
+import net.shibboleth.shared.collection.CollectionSupport;
+
+/**
+ * A function that obtains
+ * {@link DefaultStatusListConfiguration#getStatusListSignatureSigningConfiguration(ProfileRequestContext)}
+ * if such a profile is available from a {@link RelyingPartyContext} obtained via
+ * a lookup function, by default a child of the {@link ProfileRequestContext}.
+ * The value is returned as a single-valued list.
+ *
+ * <p>
+ * If a specific setting is unavailable, an empty list value is returned.
+ * </p>
+ *
+ */
+public class StatusListSignatureSigningConfigurationLookupFunction
+        extends AbstractRelyingPartyLookupFunction<List<SignatureSigningConfiguration>> {
+
+    /** {@inheritDoc} */
+    @Override
+    @Nonnull
+    public List<SignatureSigningConfiguration> apply(@Nullable final ProfileRequestContext input) {
+        final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
+        if (rpc != null) {
+            final ProfileConfiguration pc = rpc.getProfileConfig();
+            if (pc instanceof DefaultStatusListConfiguration slpc) {
+                final SignatureSigningConfiguration config =
+                        slpc.getStatusListSignatureSigningConfiguration(input);
+                if (config != null) {
+                    return CollectionSupport.listOf(config);
+                }
+            }
+        }
+        return CollectionSupport.emptyList();
+    }
+}
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/config/OpenIDVCIConfiguration.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/config/OpenIDVCIConfiguration.java
index 745f057..d77411f 100644
--- a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/config/OpenIDVCIConfiguration.java
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/config/OpenIDVCIConfiguration.java
@@ -25,6 +25,7 @@ import javax.annotation.Nullable;
 import org.opensaml.profile.context.ProfileRequestContext;
 
 import net.shibboleth.oidc.jwt.claims.ClaimsValidator;
+import net.shibboleth.oidc.security.jose.SignatureSigningConfiguration;
 import net.shibboleth.oidc.security.jose.SignatureValidationConfiguration;
 import net.shibboleth.profile.config.ConditionalProfileConfiguration;
 import net.shibboleth.shared.annotation.ConfigurationSetting;
@@ -126,6 +127,23 @@ public interface OpenIDVCIConfiguration extends ConditionalProfileConfiguration
     @Nullable SignatureValidationConfiguration getProofSignatureValidationConfiguration(
             @Nullable final ProfileRequestContext profileRequestContext);
     
+    /**
+     * Get the {@link SignatureSigningConfiguration} used for signing issued Credentials.
+     *
+     * <p>
+     * Configuration sets signing keys and algorithms deployment allows at all.
+     * Algorithm is narrowed still by credential_signing_alg_values_supported of
+     * Credential Configuration in issue.
+     * </p>
+     *
+     * @param profileRequestContext current profile request context
+     *
+     * @return the signature signing configuration to use
+     */
+    @ConfigurationSetting(name="credentialSignatureSigningConfiguration")
+    @Nullable SignatureSigningConfiguration getCredentialSignatureSigningConfiguration(
+            @Nullable final ProfileRequestContext profileRequestContext);
+
     /**
      * Get the {@link ClaimsValidator} to apply to Proof JWTs being validated by this profile.
      * 
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/config/impl/AbstractOpenIDVCIConfiguration.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/config/impl/AbstractOpenIDVCIConfiguration.java
index 9f61f7e..85a0e4b 100644
--- a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/config/impl/AbstractOpenIDVCIConfiguration.java
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/config/impl/AbstractOpenIDVCIConfiguration.java
@@ -18,7 +18,6 @@ package org.geant.shibboleth.plugin.openidvci.config.impl;
 
 import java.time.Duration;
 import java.util.function.Function;
-import java.util.function.Predicate;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -28,12 +27,12 @@ import org.geant.shibboleth.plugin.openidvci.config.impl.stolen.AbstractOIDCSSOC
 import org.opensaml.profile.context.ProfileRequestContext;
 
 import net.shibboleth.oidc.jwt.claims.ClaimsValidator;
+import net.shibboleth.oidc.security.jose.SignatureSigningConfiguration;
 import net.shibboleth.oidc.security.jose.SignatureValidationConfiguration;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.annotation.constraint.Positive;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.logic.FunctionSupport;
-import net.shibboleth.shared.logic.PredicateSupport;
 
 public abstract class AbstractOpenIDVCIConfiguration extends AbstractOIDCSSOConfiguration
         implements OpenIDVCIConfiguration {
@@ -54,6 +53,10 @@ public abstract class AbstractOpenIDVCIConfiguration extends AbstractOIDCSSOConf
     @Nonnull
     private Function<ProfileRequestContext, SignatureValidationConfiguration> proofSignatureValidationConfigurationLookupStrategy;
 
+    /** Signing of issued credentials. */
+    @Nonnull
+    private Function<ProfileRequestContext, SignatureSigningConfiguration> credentialSignatureSigningConfigurationLookupStrategy;
+
     /** Validation of JWT claims for proofs. */
     @Nonnull
     private Function<ProfileRequestContext, ClaimsValidator> proofClaimsValidatorLookupStrategy;
@@ -71,6 +74,7 @@ public abstract class AbstractOpenIDVCIConfiguration extends AbstractOIDCSSOConf
         preauthorizedCodeLengthLookupStrategy = FunctionSupport.constant(Integer.valueOf(0));
         credentialLifetimeLookupStrategy = FunctionSupport.constant(Duration.ofDays(180));
         proofSignatureValidationConfigurationLookupStrategy = FunctionSupport.constant(null);
+        credentialSignatureSigningConfigurationLookupStrategy = FunctionSupport.constant(null);
         proofClaimsValidatorLookupStrategy = FunctionSupport.constant(null);
         proofNonceGeneratorLookupStrategy = FunctionSupport.constant(null);
     }
@@ -181,6 +185,38 @@ public abstract class AbstractOpenIDVCIConfiguration extends AbstractOIDCSSOConf
                 "Lookup strategy cannot be null");
     }
 
+    /** {@inheritDoc} */
+    @Override
+    @Nullable
+    public SignatureSigningConfiguration getCredentialSignatureSigningConfiguration(
+            @Nullable final ProfileRequestContext profileRequestContext) {
+        return credentialSignatureSigningConfigurationLookupStrategy.apply(profileRequestContext);
+    }
+
+    /**
+     * Set the {@link SignatureSigningConfiguration} to sign issued credentials with.
+     *
+     * @param configuration configuration to use
+     *
+     */
+    public void setCredentialSignatureSigningConfiguration(
+            @Nullable final SignatureSigningConfiguration configuration) {
+        credentialSignatureSigningConfigurationLookupStrategy = FunctionSupport.constant(configuration);
+    }
+
+    /**
+     * Set a lookup strategy for the {@link SignatureSigningConfiguration} to sign
+     * issued credentials with.
+     *
+     * @param strategy lookup strategy
+     *
+     */
+    public void setCredentialSignatureSigningConfigurationLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, SignatureSigningConfiguration> strategy) {
+        credentialSignatureSigningConfigurationLookupStrategy = Constraint.isNotNull(strategy,
+                "Lookup strategy cannot be null");
+    }
+
     /** {@inheritDoc} */
     @Override
     @Nullable
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/config/navigate/CredentialSignatureSigningConfigurationLookupFunction.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/config/navigate/CredentialSignatureSigningConfigurationLookupFunction.java
new file mode 100644
index 0000000..89c637e
--- /dev/null
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/config/navigate/CredentialSignatureSigningConfigurationLookupFunction.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2025, GÉANT
+ *
+ * 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 org.geant.shibboleth.plugin.openidvci.config.navigate;
+
+import java.util.List;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.geant.shibboleth.plugin.openidvci.config.OpenIDVCIConfiguration;
+import org.geant.shibboleth.plugin.openidvci.credential.CredentialConfiguration;
+import org.geant.shibboleth.plugin.openidvci.messaging.context.CredentialsContext;
+import org.geant.shibboleth.plugin.openidvci.security.jose.AlgorithmRestrictedSignatureSigningConfiguration;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.oidc.security.jose.SignatureSigningConfiguration;
+import net.shibboleth.profile.config.ProfileConfiguration;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.profile.context.navigate.AbstractRelyingPartyLookupFunction;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * A function that obtains
+ * {@link OpenIDVCIConfiguration#getCredentialSignatureSigningConfiguration(ProfileRequestContext)}
+ * if such a profile is available from a {@link RelyingPartyContext} obtained via
+ * a lookup function, by default a child of the {@link ProfileRequestContext}.
+ * The value is returned as a single-valued list.
+ *
+ * <p>
+ * Configuration is restricted to credential_signing_alg_values_supported of
+ * {@link CredentialConfiguration} in issue. Algorithm belongs to Credential
+ * Configuration, not to requesting client. If Credential Configuration names no
+ * algorithms, configuration is left as it is.
+ * </p>
+ *
+ * <p>
+ * If a specific setting is unavailable, an empty list value is returned.
+ * </p>
+ *
+ */
+public class CredentialSignatureSigningConfigurationLookupFunction
+        extends AbstractRelyingPartyLookupFunction<List<SignatureSigningConfiguration>> {
+
+    /** Class logger. */
+    @Nonnull
+    private Logger log = LoggerFactory.getLogger(CredentialSignatureSigningConfigurationLookupFunction.class);
+
+    /** Strategy used to locate {@link CredentialsContext} to read. */
+    @Nonnull
+    private Function<ProfileRequestContext, CredentialsContext> credentialsContextLookupStrategy;
+
+    /** Constructor. */
+    public CredentialSignatureSigningConfigurationLookupFunction() {
+        final Function<ProfileRequestContext, CredentialsContext> cclcs = new ChildContextLookup<>(
+                CredentialsContext.class).compose(new InboundMessageContextLookup());
+        assert cclcs != null;
+        credentialsContextLookupStrategy = cclcs;
+    }
+
+    /**
+     * Set strategy used to locate the {@link CredentialsContext} to read.
+     *
+     * @param strategy lookup strategy
+     */
+    public void setCredentialsContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, CredentialsContext> strategy) {
+        credentialsContextLookupStrategy = Constraint.isNotNull(strategy,
+                "CredentialsContext lookup strategy cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nonnull
+    public List<SignatureSigningConfiguration> apply(@Nullable final ProfileRequestContext input) {
+        final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
+        if (rpc != null) {
+            final ProfileConfiguration pc = rpc.getProfileConfig();
+            if (pc instanceof OpenIDVCIConfiguration vcipc) {
+                final SignatureSigningConfiguration config = vcipc.getCredentialSignatureSigningConfiguration(input);
+                if (config != null) {
+                    return CollectionSupport.listOf(restrictToCredentialAlgorithms(config, input));
+                }
+            }
+        }
+        return CollectionSupport.emptyList();
+    }
+
+    /**
+     * Restrict configuration to algorithms Credential Configuration in issue
+     * supports.
+     *
+     * @param configuration configuration to restrict
+     * @param input         current profile request context
+     *
+     * @return restricted configuration, or given one if there is nothing to
+     *         restrict with
+     */
+    @Nonnull
+    private SignatureSigningConfiguration restrictToCredentialAlgorithms(
+            @Nonnull final SignatureSigningConfiguration configuration,
+            @Nullable final ProfileRequestContext input) {
+
+        final CredentialsContext ctx = credentialsContextLookupStrategy.apply(input);
+        final CredentialConfiguration credentialConfiguration = ctx != null ? ctx.getCredentialConfiguration() : null;
+        if (credentialConfiguration == null) {
+            log.debug("No credential configuration available, signing algorithms not restricted");
+            return configuration;
+        }
+
+        final List<String> supported = credentialConfiguration.getCredentialSigningAlgValuesSupported();
+        if (supported == null || supported.isEmpty()) {
+            log.debug("Credential configuration declares no credential_signing_alg_values_supported, "
+                    + "signing algorithms not restricted");
+            return configuration;
+        }
+
+        final SignatureSigningConfiguration restricted =
+                new AlgorithmRestrictedSignatureSigningConfiguration(configuration, supported);
+        if (restricted.getSignatureAlgorithms().isEmpty()) {
+            log.warn("None of the credential_signing_alg_values_supported {} of the credential configuration "
+                    + "are offered by the signing configuration", supported);
+        }
+        return restricted;
+    }
+
+}
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/security/jose/AlgorithmRestrictedSignatureSigningConfiguration.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/security/jose/AlgorithmRestrictedSignatureSigningConfiguration.java
new file mode 100644
index 0000000..c684504
--- /dev/null
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/security/jose/AlgorithmRestrictedSignatureSigningConfiguration.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2025, GÉANT
+ *
+ * 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 org.geant.shibboleth.plugin.openidvci.security.jose;
+
+import java.util.Collection;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.security.credential.Credential;
+
+import net.shibboleth.oidc.security.jose.SignatureSigningConfiguration;
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
+import net.shibboleth.shared.annotation.constraint.NotLive;
+import net.shibboleth.shared.annotation.constraint.Unmodifiable;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * {@link SignatureSigningConfiguration} offering restricted set of signature
+ * algorithms. Everything else is taken from wrapped configuration.
+ *
+ * <p>
+ * Restricted set is intersection of algorithms of wrapped configuration and ones
+ * asked here, in order of the request. Caller may narrow algorithms of single
+ * message without escaping include/exclude policy or signing keys deployer has
+ * configured.
+ * </p>
+ */
+public class AlgorithmRestrictedSignatureSigningConfiguration implements SignatureSigningConfiguration {
+
+    /** Configuration giving the keys and algorithm policy. */
+    @Nonnull
+    private final SignatureSigningConfiguration wrapped;
+
+    /** Algorithms left after restriction. */
+    @Nonnull
+    @NonnullElements
+    @Unmodifiable
+    @NotLive
+    private final List<String> algorithms;
+
+    /**
+     * Constructor.
+     *
+     * @param configuration configuration to take the rest from
+     * @param restrictTo    algorithms to restrict to, in preference order
+     */
+    public AlgorithmRestrictedSignatureSigningConfiguration(
+            @Nonnull final SignatureSigningConfiguration configuration,
+            @Nonnull @NonnullElements final Collection<String> restrictTo) {
+        wrapped = Constraint.isNotNull(configuration, "Signature signing configuration cannot be null");
+        Constraint.isNotNull(restrictTo, "Algorithms to restrict to cannot be null");
+
+        final List<String> offered = configuration.getSignatureAlgorithms();
+        algorithms = CollectionSupport
+                .copyToList(restrictTo.stream().filter(offered::contains).toList());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nonnull
+    @NonnullElements
+    @Unmodifiable
+    @NotLive
+    public List<String> getSignatureAlgorithms() {
+        return algorithms;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nonnull
+    @NonnullElements
+    @Unmodifiable
+    @NotLive
+    public List<Credential> getSigningCredentials() {
+        return wrapped.getSigningCredentials();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nonnull
+    @Unmodifiable
+    @NotLive
+    public Collection<String> getIncludedAlgorithms() {
+        return wrapped.getIncludedAlgorithms();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isIncludeMerge() {
+        return wrapped.isIncludeMerge();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nonnull
+    @Unmodifiable
+    @NotLive
+    public Collection<String> getExcludedAlgorithms() {
+        return wrapped.getExcludedAlgorithms();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isExcludeMerge() {
+        return wrapped.isExcludeMerge();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nonnull
+    public Precedence getIncludeExcludePrecedence() {
+        return wrapped.getIncludeExcludePrecedence();
+    }
+
+}
diff --git a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth/status-list/list/list-beans.xml b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth/status-list/list/list-beans.xml
index 7e54537..135a6cf 100644
--- a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth/status-list/list/list-beans.xml
+++ b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth/status-list/list/list-beans.xml
@@ -22,19 +22,15 @@
         c:strategy-ref="shibboleth.MessageContextLookup.Outbound">
     <property name="configurationLookupStrategy">
       <bean lazy-init="true"
-            class="net.shibboleth.oidc.profile.config.navigate.JWTSignatureSigningConfigurationLookupFunction" />
+            class="org.geant.shibboleth.plugin.oauth.statuslist.config.navigate.StatusListSignatureSigningConfigurationLookupFunction" />
     </property>
-    <!-- CHANGE TO Basic and configure sec conf having correct type of key only-->
-    <!-- Security configuration in general needs lot of work still -->
+    <!--
+    Status List Token is signed with issuer's own key. Basic resolver is used, client
+    information aware one would offer client secret as signing credential. There is no
+    client to ask algorithm from either, endpoint is unauthenticated and serves verifiers.
+    -->
     <property name="signatureSigningParametersResolver">
-            <bean class="net.shibboleth.oidc.security.jose.impl.ClientInformationSignatureSigningParametersResolver">
-                <constructor-arg name="signatureAlgorithmLookupStrategy">
-                    <bean
-                        class="net.shibboleth.oidc.profile.config.navigate.ClientInformationStringValueLookupFunction"
-                        c:keyName="id_token_signed_response_alg" />
-                </constructor-arg>
-                <constructor-arg name="defaultAlgorithmValue" value="ES256" />
-            </bean>
+      <bean class="net.shibboleth.oidc.security.jose.impl.BasicSignatureSigningParametersResolver" />
     </property>
   </bean>
     
diff --git a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/credentials/credentials-beans.xml b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/credentials/credentials-beans.xml
index 254cc31..80a9570 100644
--- a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/credentials/credentials-beans.xml
+++ b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/credentials/credentials-beans.xml
@@ -115,17 +115,16 @@
             c:strategy-ref="shibboleth.MessageContextLookup.Inbound">
         <property name="configurationLookupStrategy">
             <bean lazy-init="true"
-                class="net.shibboleth.oidc.profile.config.navigate.JWTSignatureSigningConfigurationLookupFunction" />
+                class="org.geant.shibboleth.plugin.openidvci.config.navigate.CredentialSignatureSigningConfigurationLookupFunction" />
         </property>
+        <!--
+        Credential is signed with issuer's own key. Basic resolver is used, client
+        information aware one would offer client secret as signing credential. Algorithm
+        comes from Credential Configuration, not from client. OpenID4VCI has no parameter
+        for wallet to ask it.
+        -->
         <property name="signatureSigningParametersResolver">
-            <bean class="net.shibboleth.oidc.security.jose.impl.ClientInformationSignatureSigningParametersResolver">
-                <constructor-arg name="signatureAlgorithmLookupStrategy">
-                    <bean
-                        class="net.shibboleth.oidc.profile.config.navigate.ClientInformationStringValueLookupFunction"
-                        c:keyName="id_token_signed_response_alg" />
-                </constructor-arg>
-                <constructor-arg name="defaultAlgorithmValue" value="ES256" />
-            </bean>
+            <bean class="net.shibboleth.oidc.security.jose.impl.BasicSignatureSigningParametersResolver" />
         </property>
          <property name="securityParametersContextLookupStrategy">
             <bean parent="shibboleth.Functions.Compose"
diff --git a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
index 50f4861..7ef642a 100644
--- a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
+++ b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
@@ -32,6 +32,7 @@
         p:dpopProofSignatureValidationConfiguration="#{getObject('DPoPSignatureValidationConfiguration')}"
         p:dpopProofNonceGenerator="#{getObject('DefaultOAuth2DPoPNonceGenerator')}"
         p:proofSignatureValidationConfiguration="#{getObject('openidvci.ProofSignatureValidationConfiguration')}"
+        p:credentialSignatureSigningConfiguration="#{getObject('openidvci.SigningConfiguration')}"
         p:proofNonceGenerator="#{getObject('openidvci.DefaultOpenIDVCINonceGenerator')}"
         p:proofClaimsValidator="#{getObject('openidvci.DefaultProofBodyClaimsValidator')}" />
         
@@ -49,7 +50,8 @@
 
     <bean id="OAuth.StatusList" lazy-init="true"
         class="org.geant.shibboleth.plugin.oauth.statuslist.config.DefaultStatusListConfiguration"
-        p:securityConfiguration-ref="%{idp.security.oidc.config:shibboleth.oidc.DefaultSecurityConfiguration}" />
+        p:securityConfiguration-ref="%{idp.security.oidc.config:shibboleth.oidc.DefaultSecurityConfiguration}"
+        p:statusListSignatureSigningConfiguration="#{getObject('oauth.statuslist.SigningConfiguration')}" />
 
     <bean id="OAuth.StatusList.Revoke" parent="AbstractVCIProfile" lazy-init="true"
         class="org.geant.shibboleth.plugin.oauth.statuslist.config.DefaultStatusListRevokeConfiguration" />
@@ -96,6 +98,41 @@
         </property>
     </bean>
 
+    <!--
+    Signing configuration of issued Credentials. Keys are OP's response signing credentials,
+    unless deployer defines list bean "openidvci.SigningCredentials". Then only those are
+    used. Own key must be published also, see conf/openid-vci-credentials.xml.
+
+    Only EC algorithms are offered, signing actions support nothing else. Used algorithm is
+    narrowed still per Credential Configuration by credential_signing_alg_values_supported.
+    -->
+    <bean id="openidvci.SigningConfiguration"
+        parent="shibboleth.oidc.BasicSignatureSigningConfiguration"
+        p:signingCredentials="#{getObject('openidvci.SigningCredentials') ?: getObject('shibboleth.oidc.SigningCredentialsFactory')}">
+        <property name="signatureAlgorithms">
+            <list>
+                <util:constant
+                    static-field="net.shibboleth.oidc.jwa.support.SignatureConstants.ALGO_ID_SIGNATURE_ES_256" />
+                <util:constant
+                    static-field="net.shibboleth.oidc.jwa.support.SignatureConstants.ALGO_ID_SIGNATURE_ES_384" />
+                <util:constant
+                    static-field="net.shibboleth.oidc.jwa.support.SignatureConstants.ALGO_ID_SIGNATURE_ES_512" />
+            </list>
+        </property>
+    </bean>
+
+    <!--
+    Signing configuration of Status List Tokens. Kept separate from Credential one, status
+    list is general purpose mechanism and does not serve only Verifiable Credentials. Keys
+    default still to same ones.
+
+    Status List Token names same issuer as Credentials referring to it. While issuer is
+    did:jwk, issuer IS the signing key. Own key here means Credential refers to status list
+    of some other issuer. Separate the keys only when openidvci.issuer names real issuer.
+    -->
+    <bean id="oauth.statuslist.SigningConfiguration" parent="openidvci.SigningConfiguration"
+        p:signingCredentials="#{getObject('oauth.statuslist.SigningCredentials') ?: getObject('openidvci.SigningCredentials') ?: getObject('shibboleth.oidc.SigningCredentialsFactory')}" />
+
     <bean id="openidvci.ProofSignatureValidationConfiguration"
         parent="shibboleth.oidc.BasicSignatureValidationConfiguration"
         p:signatureTrustEngine-ref="openidvci.TokenKeyTrustEngineForProofJWT"/>
diff --git a/openid-vci-impl/src/main/resources/org/geant/shibboleth/plugin/openidvci/conf/openid-vci-credentials.xml b/openid-vci-impl/src/main/resources/org/geant/shibboleth/plugin/openidvci/conf/openid-vci-credentials.xml
new file mode 100644
index 0000000..1f72c13
--- /dev/null
+++ b/openid-vci-impl/src/main/resources/org/geant/shibboleth/plugin/openidvci/conf/openid-vci-credentials.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+    xmlns:context="http://www.springframework.org/schema/context"
+    xmlns:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
+    xmlns:c="http://www.springframework.org/schema/c" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+
+    default-init-method="initialize" default-destroy-method="destroy">
+
+    <!--
+    This file defines with which key issued Verifiable Credentials are signed. This file
+    should be imported into credentials.xml.
+
+    Nothing here is active by default. Credentials are signed with OP's own response
+    signing credentials, the ones in shibboleth.oidc.SigningCredentials. Keeping it that
+    way needs nothing to be done.
+
+    Uncomment beans below to sign Credentials with own key instead. Note that Credential
+    is verified long after issuing it, by wallet or verifier having no relation to your
+    OP. Own key is not private matter between your deployment and its clients. See
+    "Publishing" below.
+    -->
+
+    <!--
+    Signing key of Verifiable Credentials only. Must be EC key, signing actions support
+    nothing else, and it should match one of algorithms you advertise in
+    credential_signing_alg_values_supported of your Credential Issuer metadata. Generate
+    one with bin/jwtgen.sh, same way as OP's own keys.
+    -->
+    <!--
+    <bean id="openidvci.DefaultESSigningCredential" parent="shibboleth.JWKCredential"
+        p:resource="%{idp.signing.openidvci.es.key}" />
+    -->
+
+    <!--
+    Lists ALL credentials used for signing issued Verifiable Credentials. Defining this
+    list REPLACES OP's credentials in Credential signing, it does not add to them. So list
+    every key you want to be considered. First one compatible with used algorithm is the
+    one signing.
+
+    Leave this undefined to keep sharing OP's signing credentials.
+    -->
+    <!--
+    <util:list id="openidvci.SigningCredentials">
+        <ref bean="openidvci.DefaultESSigningCredential" />
+    </util:list>
+    -->
+
+    <!--
+    Status List Tokens are signed separately from Credentials. Status list is general
+    purpose mechanism and does not serve only Verifiable Credentials. By default they are
+    signed with same key as Credentials, so nothing needs to be done here.
+
+    Define this list ONLY if status list must be signed with own key. Status List Token
+    names same issuer as Credentials referring to it. While that issuer is did:jwk, issuer
+    IS the key: separating them makes your Credentials refer to status list that looks like
+    issued by somebody else. Separate them only when openidvci.issuer names real issuer.
+    -->
+    <!--
+    <util:list id="oauth.statuslist.SigningCredentials">
+        <ref bean="openidvci.DefaultESSigningCredential" />
+    </util:list>
+    -->
+
+    <!--
+    Publishing.
+
+    Wallet or verifier finds your public key from key set OP publishes in its jwks_uri.
+    That key set has shibboleth.oidc.SigningCredentials in it, so key defined above is NOT
+    published. Credentials signed with it CANNOT be verified before you say otherwise.
+
+    To publish it, define shibboleth.oidc.SigningCredentialsToPublish in oidc-credentials.xml.
+    That bean REPLACES published set also, it does not add to it. So it must list OP's own
+    signing credentials too, or clients can not verify id tokens anymore. List looks like
+    this:
+
+    <util:list id="shibboleth.oidc.SigningCredentialsToPublish">
+        <ref bean="shibboleth.oidc.DefaultRSSigningCredential" />
+        <ref bean="shibboleth.oidc.DefaultESSigningCredential" />
+        <ref bean="openidvci.DefaultESSigningCredential" />
+    </util:list>
+
+    Note that same list is also set of keys trusted in validation of JWTs your deployment
+    has issued to itself, logout tokens for example. Credential signing key becomes trusted
+    there too.
+
+    Publish the key before you start signing with it. Verifier having fetched key set
+    earlier may still have old one in cache.
+    -->
+
+</beans>
diff --git a/openid-vci-impl/src/main/resources/org/geant/shibboleth/plugin/openidvci/conf/openid-vci.properties b/openid-vci-impl/src/main/resources/org/geant/shibboleth/plugin/openidvci/conf/openid-vci.properties
index d52d02a..fe0350a 100644
--- a/openid-vci-impl/src/main/resources/org/geant/shibboleth/plugin/openidvci/conf/openid-vci.properties
+++ b/openid-vci-impl/src/main/resources/org/geant/shibboleth/plugin/openidvci/conf/openid-vci.properties
@@ -2,3 +2,8 @@ oauth.statuslist.indexPermutationKey=change_this_to_secret
 oauth.statuslist.ttl=PT1H
 oauth.statuslist.assignmentRetention=P7D
 openidvci.issuer=
+
+# Signing key of issued Verifiable Credentials. Used only if you enable credential of same
+# name in conf/openid-vci-credentials.xml. Until that, Credentials are signed with OP's own
+# signing credentials. Must be EC key.
+idp.signing.openidvci.es.key = %{idp.home}/credentials/openid-vci-signing-es.jwk
diff --git a/openid-vci-impl/src/main/resources/org/geant/shibboleth/plugin/openidvci/module.properties b/openid-vci-impl/src/main/resources/org/geant/shibboleth/plugin/openidvci/module.properties
index 8a2e099..f65119b 100644
--- a/openid-vci-impl/src/main/resources/org/geant/shibboleth/plugin/openidvci/module.properties
+++ b/openid-vci-impl/src/main/resources/org/geant/shibboleth/plugin/openidvci/module.properties
@@ -12,3 +12,10 @@ OpenID-VCI.url = https://github.com/CSCfi/shibboleth-idp-oauth2-deviceflow-exten
 
 OpenID-VCI.1.src = /org/geant/shibboleth/plugin/openidvci/conf/openid-vci.properties
 OpenID-VCI.1.dest = conf/openid-vci.properties
+
+OpenID-VCI.2.src = /org/geant/shibboleth/plugin/openidvci/conf/openid-vci-credentials.xml
+OpenID-VCI.2.dest = conf/openid-vci-credentials.xml
+
+OpenID-VCI.postenable = Add <import resource="openid-vci-credentials.xml" /> in conf/credentials.xml \
+if you want to sign Verifiable Credentials with own key. Until that, Credentials are signed with \
+OP's own signing credentials.

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


More information about the commits mailing list