[java-idp-plugin-vci] 01/02: Signing configuration for signing status lists

Codeberg noreply at shibboleth.net
Tue Oct 6 11:12:33 UTC 2026


This is an automated email from the git hooks/post-receive script.

codeberg pushed a commit to branch fix/StatusListKeys
in repository java-idp-plugin-vci.

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

commit 0e71ab56450452f7c6b81c435ce072a654891e3c
Author: Janne Lauros <janne.lauros at csc.fi>
AuthorDate: Tue Oct 6 14:11:15 2026 +0300

    Signing configuration for signing status lists
---
 README.md                                          | 191 +++++++++++++++++++--
 .../conf/authn/oauth2client-authn-config.xml       |  16 ++
 .../conf/authn/oauth2client-authn-config.xml       |  16 ++
 docs/example/step-3/conf/attribute-filter.xml      |   8 +
 .../conf/authn/oauth2client-authn-config.xml       |  16 ++
 docs/example/step-3/conf/relying-party.xml         |  18 ++
 docs/example/step-3/metadata/oidc-client.json      |  13 ++
 docs/example/step-4/conf/attribute-filter.xml      |   8 +
 .../conf/authn/oauth2client-authn-config.xml       |  16 ++
 docs/example/step-4/conf/relying-party.xml         |  18 ++
 docs/example/step-4/metadata/oidc-client.json      |  13 ++
 docs/example/step-5/conf/attribute-filter.xml      |   8 +
 .../conf/authn/oauth2client-authn-config.xml       |  17 ++
 .../example/step-5/conf/openid-vci-credentials.xml |   1 +
 docs/example/step-5/conf/relying-party.xml         |  18 ++
 docs/example/step-5/metadata/oidc-client.json      |  13 ++
 .../impl/DefaultDidDocumentConfiguration.java      | 128 ++++++++++++++
 ...ignatureSigningConfigurationLookupFunction.java |  69 ++++++++
 .../FormOutboundDidDocumentResponseMessage.java    |  52 +++---
 .../profile/impl/FormStatusListToken.java          |  36 ++--
 .../profile/impl/SignStatusListToken.java          |  44 +++--
 .../idp/flows/did/document/document-beans.xml      |   4 +-
 .../flows/oauth/status-list/list/list-beans.xml    |   4 +-
 .../idp/service/relying-party/postconfig.xml       |   7 +-
 24 files changed, 653 insertions(+), 81 deletions(-)

diff --git a/README.md b/README.md
index 735daa8..3af5321 100644
--- a/README.md
+++ b/README.md
@@ -19,6 +19,7 @@
     - [Credential Configurations](#credential-configurations)
     - [Settings of your own, per credential](#settings-of-your-own-per-credential)
     - [Metadata endpoints](#metadata-endpoints)
+    - [Wallet authentication](#wallet-authentication)
     - [Pre-authorized code flow](#pre-authorized-code-flow)
     - [Authorization code flow](#authorization-code-flow)
     - [Status List](#status-list)
@@ -254,7 +255,9 @@ of the OIDC OP wiki page. A normal installation of the OP plugin has one,
    a certificate chain.
 6. Rolling the signing key to the next one.
 
-For steps 1 to 4 the wallet is [Paradym](https://paradym.id). At the end the installation
+The wallet of steps 1 and 2 is [Paradym](https://paradym.id). Paradym accepts only did and x5c
+issuers, so the url credential of steps 3 and 4 goes to the TOPPAN wallet. The HAIP credentials of
+Step 5 are tested with the OpenID conformance suite. At the end the installation
 releases did:jwk, did:web, plain url, OpenID Federation and HAIP credentials.
 
 `https://issuer.example.org` is your IdP's OIDC issuer everywhere below.
@@ -659,6 +662,29 @@ Full file: [etc/jetty-rewrite-rules.xml](docs/example/step-1/etc/jetty-rewrite-r
 
 </details>
 
+#### Wallet authentication
+
+**File(s):** *conf/authn/oauth2client-authn-config.xml*
+**Format:** Native Spring
+
+The wallets of both flows below are public clients, they authenticate at the token endpoint with
+`none`. You add the validator of public clients to the client validators:
+
+<details>
+<summary>conf/authn/oauth2client-authn-config.xml</summary>
+
+```xml
+<util:list id="shibboleth.authn.OAuth2Client.Validators">
+    <ref bean="shibboleth.OIDCClientInfoValidator" />
+    <ref bean="shibboleth.JWTValidator" />
+    <ref bean="openidvci.PublicClientValidator" />
+</util:list>
+```
+
+Full file: [conf/authn/oauth2client-authn-config.xml](docs/example/step-1/conf/authn/oauth2client-authn-config.xml)
+
+</details>
+
 #### Pre-authorized code flow
 
 **File(s):** *metadata/oidc-client.json*, *conf/relying-party.xml*
@@ -1773,6 +1799,13 @@ What is new:
 - JWT VC issuer metadata is published at `/.well-known/jwt-vc-issuer`
 - the credential header `kid` is the plain name of the key, as in the keyset of the OP
 - the Status List url of the credential ends with `~url`
+- the TOPPAN wallet is a second client, with a relying party and an attribute release of its own
+
+Paradym does not take a credential whose issuer is a plain url, it accepts did and x5c issuers
+only. The url credential is fetched with TOPPAN instead. TOPPAN sends the client id
+`wallet-dev` and the redirect uri `walletapp-goid-openid4vci-callback://authorization`, and
+these are what the client entry has. TOPPAN binds the PAR to its DPoP key already, so the code
+and the access token are bound to that key.
 
 Each file as it is after this step, the changes marked:
 
@@ -2008,6 +2041,24 @@ Full file: [metadata/verifiable-credentials.json](docs/example/step-3/metadata/v
            </property>
          </bean>
  
++        <bean parent="RelyingPartyByName"
++              c:relyingPartyIds="#{{'wallet-dev'}}">
++          <property name="profileConfigurations">
++              <list>
++                  <bean parent="OIDC.SSO"
++                        p:authorizationCodeClaimsSetManipulationStrategy-ref="openidvci.TokenManipulationStrategy"
++                        p:forcePKCE="true"
++                        p:requirePushedAuthorizationRequest="true" />
++                  <bean parent="OpenID.VCI.Token"
++                        p:tokenEndpointAuthMethods="#{{'none'}}"
++                        p:accessTokenClaimsSetManipulationStrategy-ref="openidvci.TokenManipulationStrategy" />
++                  <bean parent="OpenID.VCI.Credentials" />
++                  <bean parent="OAUTH2.PAR" p:tokenEndpointAuthMethods="#{{'none'}}"
++                        p:requestUriClaimsSetManipulationStrategy-ref="openidvci.PushedAuthorizationDPoPJktManipulationStrategy" />
++              </list>
++          </property>
++        </bean>
++
      </util:list>
  
  </beans>
@@ -2127,7 +2178,21 @@ Full file: [etc/jetty-rewrite-rules.xml](docs/example/step-3/etc/jetty-rewrite-r
        "authorization_code",
        "urn:ietf:params:oauth:grant-type:pre-authorized_code"
      ]
-   }
+-  }
++  },
++  {
++    "client_id": "wallet-dev",
++    "client_name": "TOPPAN Wallet",
++    "redirect_uris": ["walletapp-goid-openid4vci-callback://authorization"],
++    "scope": "openid GeantIncubatorDiploma_URL",
++    "audience": [
++      "credentials",
++      "urn:shibboleth.oidc.op.resource:https://issuer.example.org"
++    ],
++    "token_endpoint_auth_method": "none",
++    "response_types": ["code"],
++    "grant_types": ["authorization_code"]
++  }
  ]
 ```
 
@@ -2135,6 +2200,39 @@ Full file: [metadata/oidc-client.json](docs/example/step-3/metadata/oidc-client.
 
 </details>
 
+<details>
+<summary>conf/attribute-filter.xml</summary>
+
+```diff
+ <?xml version="1.0" encoding="UTF-8"?>
+ <AttributeFilterPolicyGroup id="ShibbolethFilterPolicy"
+         xmlns="urn:mace:shibboleth:2.0:afp"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="urn:mace:shibboleth:2.0:afp http://shibboleth.net/schema/idp/shibboleth-afp.xsd">
+ 
+     <AttributeFilterPolicy id="GeantIncubatorDiplomaToWallets">
+         <PolicyRequirementRule xsi:type="Requester"
+             value="id.animo.paradym" />
+         <AttributeRule attributeID="GeantIncubatorDiploma">
+             <PermitValueRule xsi:type="ANY" />
+         </AttributeRule>
+     </AttributeFilterPolicy>
+ 
++    <AttributeFilterPolicy id="GeantIncubatorDiplomaToToppan">
++        <PolicyRequirementRule xsi:type="Requester"
++            value="wallet-dev" />
++        <AttributeRule attributeID="GeantIncubatorDiploma">
++            <PermitValueRule xsi:type="ANY" />
++        </AttributeRule>
++    </AttributeFilterPolicy>
++
+ </AttributeFilterPolicyGroup>
+```
+
+Full file: [conf/attribute-filter.xml](docs/example/step-3/conf/attribute-filter.xml)
+
+</details>
+
 <details>
 <summary>static/oauth-authorization-server.json</summary>
 
@@ -2204,6 +2302,8 @@ What is new:
   metadata and the credential signing keys
 - the url credential carries `"fed": "https://issuer.example.org"`, the other credentials not
 
+The url credential is still fetched with TOPPAN, the client of Step 3.
+
 The Entity Configuration publishes the keys of **openidvci.SigningCredentials** of Step 2. If
 the federation file is imported and the list is not defined, the IdP does not start.
 
@@ -2472,6 +2572,24 @@ Full file: [metadata/verifiable-credentials.json](docs/example/step-4/metadata/v
            </property>
          </bean>
  
+         <bean parent="RelyingPartyByName"
+               c:relyingPartyIds="#{{'wallet-dev'}}">
+           <property name="profileConfigurations">
+               <list>
+                   <bean parent="OIDC.SSO"
+                         p:authorizationCodeClaimsSetManipulationStrategy-ref="openidvci.TokenManipulationStrategy"
+                         p:forcePKCE="true"
+                         p:requirePushedAuthorizationRequest="true" />
+                   <bean parent="OpenID.VCI.Token"
+                         p:tokenEndpointAuthMethods="#{{'none'}}"
+                         p:accessTokenClaimsSetManipulationStrategy-ref="openidvci.TokenManipulationStrategy" />
+                   <bean parent="OpenID.VCI.Credentials" />
+                   <bean parent="OAUTH2.PAR" p:tokenEndpointAuthMethods="#{{'none'}}"
+                         p:requestUriClaimsSetManipulationStrategy-ref="openidvci.PushedAuthorizationDPoPJktManipulationStrategy" />
+               </list>
+           </property>
+         </bean>
+ 
      </util:list>
  
  </beans>
@@ -2667,6 +2785,7 @@ document:
  
 +    <util:list id="openidvci.status-list.SigningCredentials">
 +        <ref bean="openidvci.X509StatusListSigningCredential" />
++        <ref bean="openidvci.X509ESSigningCredential" />
 +        <ref bean="openidvci.OPESSigningCredential" />
 +    </util:list>
 +
@@ -2683,7 +2802,9 @@ Full file: [conf/openid-vci-credentials.xml](docs/example/step-5/conf/openid-vci
 
 The credentials of Steps 1 to 4 were signed with the key of the OP, so it stays in both lists,
 after the new keys, see [Step 6](#step-6-rolling-the-signing-key). In the Status List list it signs
-the Status Lists of the did:jwk credential of Step 1.
+the Status Lists of the did:jwk credential of Step 1. The new signing key is in the Status List
+list too: from now on it signs the did:jwk credentials, and the Status List of a did:jwk
+credential is signed with the key that signed the credential.
 
 The chain is emitted whenever the issuer of the credential, Status List Token or metadata is an https url, there is no
 switch for it. That means the url credential of Steps 3 and 4 now carries `x5c` too, and no
@@ -3172,6 +3293,14 @@ release it to the HAIP wallet in *conf/attribute-filter.xml*.
          </AttributeRule>
      </AttributeFilterPolicy>
  
+     <AttributeFilterPolicy id="GeantIncubatorDiplomaToToppan">
+         <PolicyRequirementRule xsi:type="Requester"
+             value="wallet-dev" />
+         <AttributeRule attributeID="GeantIncubatorDiploma">
+             <PermitValueRule xsi:type="ANY" />
+         </AttributeRule>
+     </AttributeFilterPolicy>
+ 
 +    <AttributeFilterPolicy id="GeantIncubatorDiplomaToHAIPWallets">
 +        <PolicyRequirementRule xsi:type="Requester"
 +            value="haip-wallet" />
@@ -3217,8 +3346,8 @@ anchors such a request is refused.
 
 Wallet Attestation is a client authentication method, so it is added to the login flow of
 clients in *conf/authn/oauth2client-authn-config.xml*. It has to be before the validator of
-public clients, otherwise a public client match authenticates an attested request without
-checking the attestation:
+public clients of [Step 1](#wallet-authentication), otherwise a public client match
+authenticates an attested request without checking the attestation:
 
 ```xml
 <util:list id="shibboleth.authn.OAuth2Client.Validators">
@@ -3229,6 +3358,8 @@ checking the attestation:
 </util:list>
 ```
 
+Full file: [conf/authn/oauth2client-authn-config.xml](docs/example/step-5/conf/authn/oauth2client-authn-config.xml)
+
 The VCI endpoints use login flow `OpenIDVCIClient` of this plugin, which knows the method. The
 PAR endpoint is the OP's, and it refuses a client registered with a method its own login flow
 does not know. Give the OAuth endpoints of the OP the same flow in *conf/oidc.properties*:
@@ -3272,6 +3403,19 @@ The client id is the `sub` of the Wallet Attestation of the wallet. In *metadata
        "authorization_code",
        "urn:ietf:params:oauth:grant-type:pre-authorized_code"
      ]
+   },
+   {
+     "client_id": "wallet-dev",
+     "client_name": "TOPPAN Wallet",
+     "redirect_uris": ["walletapp-goid-openid4vci-callback://authorization"],
+     "scope": "openid GeantIncubatorDiploma_URL",
+     "audience": [
+       "credentials",
+       "urn:shibboleth.oidc.op.resource:https://issuer.example.org"
+     ],
+     "token_endpoint_auth_method": "none",
+     "response_types": ["code"],
+     "grant_types": ["authorization_code"]
 +  },
 +  {
 +    "client_id": "haip-wallet",
@@ -3390,6 +3534,24 @@ and both token and PAR endpoints take the Wallet Attestation:
            </property>
          </bean>
  
+         <bean parent="RelyingPartyByName"
+               c:relyingPartyIds="#{{'wallet-dev'}}">
+           <property name="profileConfigurations">
+               <list>
+                   <bean parent="OIDC.SSO"
+                         p:authorizationCodeClaimsSetManipulationStrategy-ref="openidvci.TokenManipulationStrategy"
+                         p:forcePKCE="true"
+                         p:requirePushedAuthorizationRequest="true" />
+                   <bean parent="OpenID.VCI.Token"
+                         p:tokenEndpointAuthMethods="#{{'none'}}"
+                         p:accessTokenClaimsSetManipulationStrategy-ref="openidvci.TokenManipulationStrategy" />
+                   <bean parent="OpenID.VCI.Credentials" />
+                   <bean parent="OAUTH2.PAR" p:tokenEndpointAuthMethods="#{{'none'}}"
+                         p:requestUriClaimsSetManipulationStrategy-ref="openidvci.PushedAuthorizationDPoPJktManipulationStrategy" />
+               </list>
+           </property>
+         </bean>
+ 
 +        <bean parent="RelyingPartyByName"
 +              c:relyingPartyIds="#{{'haip-wallet'}}">
 +          <property name="profileConfigurations">
@@ -3527,7 +3689,7 @@ them in that list too.
 |---|---|---|---|---|
 | `GeantIncubatorDiploma_SDJWT`, `_W3C` | `did:jwk:…` | the identifier | no | Paradym |
 | `GeantIncubatorDiploma_DIDWEB` | `did:web:issuer.example.org` | `/.well-known/did.json` | no | Paradym |
-| `GeantIncubatorDiploma_URL` | `https://issuer.example.org` | `x5c`, earlier `/.well-known/jwt-vc-issuer` | yes | Paradym |
+| `GeantIncubatorDiploma_URL` | `https://issuer.example.org` | `x5c`, earlier `/.well-known/jwt-vc-issuer` | yes | TOPPAN |
 | `GeantIncubatorDiploma_HAIP` | `https://issuer.example.org` | `x5c` | no | HAIP wallet |
 | `GeantIncubatorDiploma_MDOC` | none | `x5chain` | no | HAIP wallet |
 
@@ -3750,9 +3912,18 @@ Endpoint `/idp/profile/openid/vci/issuer-metadata`, profile ID `http://geant.org
 | `issuerMetadataSignatureSigningConfiguration` | SignatureSigningConfiguration | **openidvci.issuerMetadata.SigningConfiguration** | Signing of the metadata. Published unsigned until **openidvci.issuerMetadata.SigningCredentials** exists. Signed metadata is served for `Accept: application/jwt`. |
 | `outboundInterceptorFlows` | List<String> | | Interceptor flows run before the response is sent, also for an error response. |
 
-**OAuth.JWTVCIssuer** (`/idp/profile/oauth/jwt-vc-issuer`), **OAuth.AuthorizationServer**
-(`/idp/profile/oauth/authorization-server`) and **DID.Document** (`/idp/profile/did/document`)
-have one setting, `outboundInterceptorFlows`, as above.
+**OAuth.JWTVCIssuer** (`/idp/profile/oauth/jwt-vc-issuer`) and **OAuth.AuthorizationServer**
+(`/idp/profile/oauth/authorization-server`) have one setting, `outboundInterceptorFlows`, as above.
+
+#### DID.Document
+
+Endpoint `/idp/profile/did/document`, profile ID `http://geant.org/ns/profiles/did/document`.
+
+| Name | Type | Default | Description |
+|---|---|---|---|
+| `credentialSignatureSigningConfiguration` | SignatureSigningConfiguration | **openidvci.SigningConfiguration** | Its keys are published in the document. |
+| `statusListSignatureSigningConfiguration` | SignatureSigningConfiguration | **openidvci.status-list.SigningConfiguration** | Its keys are published in the document. |
+| `outboundInterceptorFlows` | List<String> | | Interceptor flows run before the response is sent, also for an error response. |
 
 #### OpenID.VCI.CredentialOffer
 
@@ -3828,7 +3999,7 @@ Endpoint `/idp/profile/oauth/status-list/list`, profile ID `http://geant.org/ns/
 
 | Name | Type | Default | Description |
 |---|---|---|---|
-| `statusListSignatureSigningConfiguration` | SignatureSigningConfiguration | **openidvci.status-list.SigningConfiguration** | Signing of Status List Tokens. |
+| `statusListSignatureSigningConfiguration` | SignatureSigningConfiguration | **openidvci.status-list.SigningConfiguration** | Signing of Status List Tokens. The list of a did:jwk credential is signed with the key its `id` names, any key of this configuration. |
 | `outboundInterceptorFlows` | List<String> | | Interceptor flows run before the response is sent, also for an error response. |
 
 Request, a `GET` with `id`, the month and the issuer tag as in the `uri` of a credential. Response
diff --git a/docs/example/step-1/conf/authn/oauth2client-authn-config.xml b/docs/example/step-1/conf/authn/oauth2client-authn-config.xml
new file mode 100644
index 0000000..f8d4131
--- /dev/null
+++ b/docs/example/step-1/conf/authn/oauth2client-authn-config.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:util="http://www.springframework.org/schema/util"
+       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/util http://www.springframework.org/schema/util/spring-util.xsd"
+       default-init-method="initialize"
+       default-destroy-method="destroy">
+
+    <util:list id="shibboleth.authn.OAuth2Client.Validators">
+        <ref bean="shibboleth.OIDCClientInfoValidator" />
+        <ref bean="shibboleth.JWTValidator" />
+        <ref bean="openidvci.PublicClientValidator" />
+    </util:list>
+
+</beans>
diff --git a/docs/example/step-2/conf/authn/oauth2client-authn-config.xml b/docs/example/step-2/conf/authn/oauth2client-authn-config.xml
new file mode 100644
index 0000000..f8d4131
--- /dev/null
+++ b/docs/example/step-2/conf/authn/oauth2client-authn-config.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:util="http://www.springframework.org/schema/util"
+       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/util http://www.springframework.org/schema/util/spring-util.xsd"
+       default-init-method="initialize"
+       default-destroy-method="destroy">
+
+    <util:list id="shibboleth.authn.OAuth2Client.Validators">
+        <ref bean="shibboleth.OIDCClientInfoValidator" />
+        <ref bean="shibboleth.JWTValidator" />
+        <ref bean="openidvci.PublicClientValidator" />
+    </util:list>
+
+</beans>
diff --git a/docs/example/step-3/conf/attribute-filter.xml b/docs/example/step-3/conf/attribute-filter.xml
index b10fb12..8973aed 100644
--- a/docs/example/step-3/conf/attribute-filter.xml
+++ b/docs/example/step-3/conf/attribute-filter.xml
@@ -12,4 +12,12 @@
         </AttributeRule>
     </AttributeFilterPolicy>
 
+    <AttributeFilterPolicy id="GeantIncubatorDiplomaToToppan">
+        <PolicyRequirementRule xsi:type="Requester"
+            value="wallet-dev" />
+        <AttributeRule attributeID="GeantIncubatorDiploma">
+            <PermitValueRule xsi:type="ANY" />
+        </AttributeRule>
+    </AttributeFilterPolicy>
+
 </AttributeFilterPolicyGroup>
diff --git a/docs/example/step-3/conf/authn/oauth2client-authn-config.xml b/docs/example/step-3/conf/authn/oauth2client-authn-config.xml
new file mode 100644
index 0000000..f8d4131
--- /dev/null
+++ b/docs/example/step-3/conf/authn/oauth2client-authn-config.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:util="http://www.springframework.org/schema/util"
+       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/util http://www.springframework.org/schema/util/spring-util.xsd"
+       default-init-method="initialize"
+       default-destroy-method="destroy">
+
+    <util:list id="shibboleth.authn.OAuth2Client.Validators">
+        <ref bean="shibboleth.OIDCClientInfoValidator" />
+        <ref bean="shibboleth.JWTValidator" />
+        <ref bean="openidvci.PublicClientValidator" />
+    </util:list>
+
+</beans>
diff --git a/docs/example/step-3/conf/relying-party.xml b/docs/example/step-3/conf/relying-party.xml
index 6bf8645..bd86516 100644
--- a/docs/example/step-3/conf/relying-party.xml
+++ b/docs/example/step-3/conf/relying-party.xml
@@ -87,6 +87,24 @@
           </property>
         </bean>
 
+        <bean parent="RelyingPartyByName"
+              c:relyingPartyIds="#{{'wallet-dev'}}">
+          <property name="profileConfigurations">
+              <list>
+                  <bean parent="OIDC.SSO"
+                        p:authorizationCodeClaimsSetManipulationStrategy-ref="openidvci.TokenManipulationStrategy"
+                        p:forcePKCE="true"
+                        p:requirePushedAuthorizationRequest="true" />
+                  <bean parent="OpenID.VCI.Token"
+                        p:tokenEndpointAuthMethods="#{{'none'}}"
+                        p:accessTokenClaimsSetManipulationStrategy-ref="openidvci.TokenManipulationStrategy" />
+                  <bean parent="OpenID.VCI.Credentials" />
+                  <bean parent="OAUTH2.PAR" p:tokenEndpointAuthMethods="#{{'none'}}"
+                        p:requestUriClaimsSetManipulationStrategy-ref="openidvci.PushedAuthorizationDPoPJktManipulationStrategy" />
+              </list>
+          </property>
+        </bean>
+
     </util:list>
 
 </beans>
diff --git a/docs/example/step-3/metadata/oidc-client.json b/docs/example/step-3/metadata/oidc-client.json
index 57f62c0..bd89fa3 100644
--- a/docs/example/step-3/metadata/oidc-client.json
+++ b/docs/example/step-3/metadata/oidc-client.json
@@ -25,5 +25,18 @@
       "authorization_code",
       "urn:ietf:params:oauth:grant-type:pre-authorized_code"
     ]
+  },
+  {
+    "client_id": "wallet-dev",
+    "client_name": "TOPPAN Wallet",
+    "redirect_uris": ["walletapp-goid-openid4vci-callback://authorization"],
+    "scope": "openid GeantIncubatorDiploma_URL",
+    "audience": [
+      "credentials",
+      "urn:shibboleth.oidc.op.resource:https://issuer.example.org"
+    ],
+    "token_endpoint_auth_method": "none",
+    "response_types": ["code"],
+    "grant_types": ["authorization_code"]
   }
 ]
diff --git a/docs/example/step-4/conf/attribute-filter.xml b/docs/example/step-4/conf/attribute-filter.xml
index b10fb12..8973aed 100644
--- a/docs/example/step-4/conf/attribute-filter.xml
+++ b/docs/example/step-4/conf/attribute-filter.xml
@@ -12,4 +12,12 @@
         </AttributeRule>
     </AttributeFilterPolicy>
 
+    <AttributeFilterPolicy id="GeantIncubatorDiplomaToToppan">
+        <PolicyRequirementRule xsi:type="Requester"
+            value="wallet-dev" />
+        <AttributeRule attributeID="GeantIncubatorDiploma">
+            <PermitValueRule xsi:type="ANY" />
+        </AttributeRule>
+    </AttributeFilterPolicy>
+
 </AttributeFilterPolicyGroup>
diff --git a/docs/example/step-4/conf/authn/oauth2client-authn-config.xml b/docs/example/step-4/conf/authn/oauth2client-authn-config.xml
new file mode 100644
index 0000000..f8d4131
--- /dev/null
+++ b/docs/example/step-4/conf/authn/oauth2client-authn-config.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:util="http://www.springframework.org/schema/util"
+       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/util http://www.springframework.org/schema/util/spring-util.xsd"
+       default-init-method="initialize"
+       default-destroy-method="destroy">
+
+    <util:list id="shibboleth.authn.OAuth2Client.Validators">
+        <ref bean="shibboleth.OIDCClientInfoValidator" />
+        <ref bean="shibboleth.JWTValidator" />
+        <ref bean="openidvci.PublicClientValidator" />
+    </util:list>
+
+</beans>
diff --git a/docs/example/step-4/conf/relying-party.xml b/docs/example/step-4/conf/relying-party.xml
index 667d8b1..4b430a1 100644
--- a/docs/example/step-4/conf/relying-party.xml
+++ b/docs/example/step-4/conf/relying-party.xml
@@ -89,6 +89,24 @@
           </property>
         </bean>
 
+        <bean parent="RelyingPartyByName"
+              c:relyingPartyIds="#{{'wallet-dev'}}">
+          <property name="profileConfigurations">
+              <list>
+                  <bean parent="OIDC.SSO"
+                        p:authorizationCodeClaimsSetManipulationStrategy-ref="openidvci.TokenManipulationStrategy"
+                        p:forcePKCE="true"
+                        p:requirePushedAuthorizationRequest="true" />
+                  <bean parent="OpenID.VCI.Token"
+                        p:tokenEndpointAuthMethods="#{{'none'}}"
+                        p:accessTokenClaimsSetManipulationStrategy-ref="openidvci.TokenManipulationStrategy" />
+                  <bean parent="OpenID.VCI.Credentials" />
+                  <bean parent="OAUTH2.PAR" p:tokenEndpointAuthMethods="#{{'none'}}"
+                        p:requestUriClaimsSetManipulationStrategy-ref="openidvci.PushedAuthorizationDPoPJktManipulationStrategy" />
+              </list>
+          </property>
+        </bean>
+
     </util:list>
 
 </beans>
diff --git a/docs/example/step-4/metadata/oidc-client.json b/docs/example/step-4/metadata/oidc-client.json
index 57f62c0..bd89fa3 100644
--- a/docs/example/step-4/metadata/oidc-client.json
+++ b/docs/example/step-4/metadata/oidc-client.json
@@ -25,5 +25,18 @@
       "authorization_code",
       "urn:ietf:params:oauth:grant-type:pre-authorized_code"
     ]
+  },
+  {
+    "client_id": "wallet-dev",
+    "client_name": "TOPPAN Wallet",
+    "redirect_uris": ["walletapp-goid-openid4vci-callback://authorization"],
+    "scope": "openid GeantIncubatorDiploma_URL",
+    "audience": [
+      "credentials",
+      "urn:shibboleth.oidc.op.resource:https://issuer.example.org"
+    ],
+    "token_endpoint_auth_method": "none",
+    "response_types": ["code"],
+    "grant_types": ["authorization_code"]
   }
 ]
diff --git a/docs/example/step-5/conf/attribute-filter.xml b/docs/example/step-5/conf/attribute-filter.xml
index b767242..5dc959d 100644
--- a/docs/example/step-5/conf/attribute-filter.xml
+++ b/docs/example/step-5/conf/attribute-filter.xml
@@ -12,6 +12,14 @@
         </AttributeRule>
     </AttributeFilterPolicy>
 
+    <AttributeFilterPolicy id="GeantIncubatorDiplomaToToppan">
+        <PolicyRequirementRule xsi:type="Requester"
+            value="wallet-dev" />
+        <AttributeRule attributeID="GeantIncubatorDiploma">
+            <PermitValueRule xsi:type="ANY" />
+        </AttributeRule>
+    </AttributeFilterPolicy>
+
     <AttributeFilterPolicy id="GeantIncubatorDiplomaToHAIPWallets">
         <PolicyRequirementRule xsi:type="Requester"
             value="haip-wallet" />
diff --git a/docs/example/step-5/conf/authn/oauth2client-authn-config.xml b/docs/example/step-5/conf/authn/oauth2client-authn-config.xml
new file mode 100644
index 0000000..b64b473
--- /dev/null
+++ b/docs/example/step-5/conf/authn/oauth2client-authn-config.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:util="http://www.springframework.org/schema/util"
+       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/util http://www.springframework.org/schema/util/spring-util.xsd"
+       default-init-method="initialize"
+       default-destroy-method="destroy">
+
+    <util:list id="shibboleth.authn.OAuth2Client.Validators">
+        <ref bean="shibboleth.OIDCClientInfoValidator" />
+        <ref bean="shibboleth.JWTValidator" />
+        <ref bean="openidvci.ClientAttestationValidator" />
+        <ref bean="openidvci.PublicClientValidator" />
+    </util:list>
+
+</beans>
diff --git a/docs/example/step-5/conf/openid-vci-credentials.xml b/docs/example/step-5/conf/openid-vci-credentials.xml
index 66f03b8..270c59e 100644
--- a/docs/example/step-5/conf/openid-vci-credentials.xml
+++ b/docs/example/step-5/conf/openid-vci-credentials.xml
@@ -36,6 +36,7 @@
 
     <util:list id="openidvci.status-list.SigningCredentials">
         <ref bean="openidvci.X509StatusListSigningCredential" />
+        <ref bean="openidvci.X509ESSigningCredential" />
         <ref bean="openidvci.OPESSigningCredential" />
     </util:list>
 
diff --git a/docs/example/step-5/conf/relying-party.xml b/docs/example/step-5/conf/relying-party.xml
index 59ab352..3719429 100644
--- a/docs/example/step-5/conf/relying-party.xml
+++ b/docs/example/step-5/conf/relying-party.xml
@@ -89,6 +89,24 @@
           </property>
         </bean>
 
+        <bean parent="RelyingPartyByName"
+              c:relyingPartyIds="#{{'wallet-dev'}}">
+          <property name="profileConfigurations">
+              <list>
+                  <bean parent="OIDC.SSO"
+                        p:authorizationCodeClaimsSetManipulationStrategy-ref="openidvci.TokenManipulationStrategy"
+                        p:forcePKCE="true"
+                        p:requirePushedAuthorizationRequest="true" />
+                  <bean parent="OpenID.VCI.Token"
+                        p:tokenEndpointAuthMethods="#{{'none'}}"
+                        p:accessTokenClaimsSetManipulationStrategy-ref="openidvci.TokenManipulationStrategy" />
+                  <bean parent="OpenID.VCI.Credentials" />
+                  <bean parent="OAUTH2.PAR" p:tokenEndpointAuthMethods="#{{'none'}}"
+                        p:requestUriClaimsSetManipulationStrategy-ref="openidvci.PushedAuthorizationDPoPJktManipulationStrategy" />
+              </list>
+          </property>
+        </bean>
+
         <bean parent="RelyingPartyByName"
               c:relyingPartyIds="#{{'haip-wallet'}}">
           <property name="profileConfigurations">
diff --git a/docs/example/step-5/metadata/oidc-client.json b/docs/example/step-5/metadata/oidc-client.json
index 76a3a9c..5fb670e 100644
--- a/docs/example/step-5/metadata/oidc-client.json
+++ b/docs/example/step-5/metadata/oidc-client.json
@@ -26,6 +26,19 @@
       "urn:ietf:params:oauth:grant-type:pre-authorized_code"
     ]
   },
+  {
+    "client_id": "wallet-dev",
+    "client_name": "TOPPAN Wallet",
+    "redirect_uris": ["walletapp-goid-openid4vci-callback://authorization"],
+    "scope": "openid GeantIncubatorDiploma_URL",
+    "audience": [
+      "credentials",
+      "urn:shibboleth.oidc.op.resource:https://issuer.example.org"
+    ],
+    "token_endpoint_auth_method": "none",
+    "response_types": ["code"],
+    "grant_types": ["authorization_code"]
+  },
   {
     "client_id": "haip-wallet",
     "client_name": "HAIP Wallet",
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/config/impl/DefaultDidDocumentConfiguration.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/config/impl/DefaultDidDocumentConfiguration.java
new file mode 100644
index 0000000..6001a3d
--- /dev/null
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/config/impl/DefaultDidDocumentConfiguration.java
@@ -0,0 +1,128 @@
+/*
+ * 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.profile.config.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.oidc.security.jose.SignatureSigningConfiguration;
+import net.shibboleth.shared.annotation.ConfigurationSetting;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.FunctionSupport;
+
+/**
+ * Profile configuration for the endpoint publishing the did:web document of this
+ * deployment.
+ */
+public class DefaultDidDocumentConfiguration extends DefaultMetadataDocumentConfiguration {
+
+    /** Signing of issued Verifiable Credentials. */
+    @Nonnull
+    private Function<ProfileRequestContext, SignatureSigningConfiguration>
+            credentialSignatureSigningConfigurationLookupStrategy;
+
+    /** Signing of Status List Tokens. */
+    @Nonnull
+    private Function<ProfileRequestContext, SignatureSigningConfiguration>
+            statusListSignatureSigningConfigurationLookupStrategy;
+
+    /** Constructor. */
+    public DefaultDidDocumentConfiguration() {
+        super(PROFILE_ID_DID_DOCUMENT);
+        credentialSignatureSigningConfigurationLookupStrategy = FunctionSupport.constant(null);
+        statusListSignatureSigningConfigurationLookupStrategy = FunctionSupport.constant(null);
+    }
+
+    /**
+     * Get the {@link SignatureSigningConfiguration} issued Verifiable Credentials are
+     * signed by.
+     *
+     * @param profileRequestContext current profile request context
+     *
+     * @return the signature signing configuration
+     */
+    @ConfigurationSetting(name="credentialSignatureSigningConfiguration")
+    @Nullable
+    public SignatureSigningConfiguration getCredentialSignatureSigningConfiguration(
+            @Nullable final ProfileRequestContext profileRequestContext) {
+        return credentialSignatureSigningConfigurationLookupStrategy.apply(profileRequestContext);
+    }
+
+    /**
+     * Set the {@link SignatureSigningConfiguration} issued Verifiable Credentials are
+     * signed by.
+     *
+     * @param configuration configuration to use
+     */
+    public void setCredentialSignatureSigningConfiguration(
+            @Nullable final SignatureSigningConfiguration configuration) {
+        credentialSignatureSigningConfigurationLookupStrategy = FunctionSupport.constant(configuration);
+    }
+
+    /**
+     * Set a lookup strategy for the {@link SignatureSigningConfiguration} issued
+     * Verifiable Credentials are signed by.
+     *
+     * @param strategy lookup strategy
+     */
+    public void setCredentialSignatureSigningConfigurationLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, SignatureSigningConfiguration> strategy) {
+        credentialSignatureSigningConfigurationLookupStrategy = Constraint.isNotNull(strategy,
+                "Lookup strategy cannot be null");
+    }
+
+    /**
+     * Get the {@link SignatureSigningConfiguration} Status List Tokens are signed by.
+     *
+     * @param profileRequestContext current profile request context
+     *
+     * @return the signature signing configuration
+     */
+    @ConfigurationSetting(name="statusListSignatureSigningConfiguration")
+    @Nullable
+    public SignatureSigningConfiguration getStatusListSignatureSigningConfiguration(
+            @Nullable final ProfileRequestContext profileRequestContext) {
+        return statusListSignatureSigningConfigurationLookupStrategy.apply(profileRequestContext);
+    }
+
+    /**
+     * Set the {@link SignatureSigningConfiguration} Status List Tokens are signed by.
+     *
+     * @param configuration configuration to use
+     */
+    public void setStatusListSignatureSigningConfiguration(
+            @Nullable final SignatureSigningConfiguration configuration) {
+        statusListSignatureSigningConfigurationLookupStrategy = FunctionSupport.constant(configuration);
+    }
+
+    /**
+     * Set a lookup strategy for the {@link SignatureSigningConfiguration} Status List
+     * Tokens are signed by.
+     *
+     * @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/openidvci/profile/config/navigate/DidDocumentSignatureSigningConfigurationLookupFunction.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/config/navigate/DidDocumentSignatureSigningConfigurationLookupFunction.java
new file mode 100644
index 0000000..e617636
--- /dev/null
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/config/navigate/DidDocumentSignatureSigningConfigurationLookupFunction.java
@@ -0,0 +1,69 @@
+/*
+ * 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.profile.config.navigate;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.geant.shibboleth.plugin.openidvci.profile.config.impl.DefaultDidDocumentConfiguration;
+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;
+
+/**
+ * A function that obtains
+ * {@link DefaultDidDocumentConfiguration#getCredentialSignatureSigningConfiguration(ProfileRequestContext)}
+ * and
+ * {@link DefaultDidDocumentConfiguration#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}.
+ *
+ * <p>
+ * If a specific setting is unavailable, it is left out of the list.
+ * </p>
+ */
+public class DidDocumentSignatureSigningConfigurationLookupFunction
+        extends AbstractRelyingPartyLookupFunction<List<SignatureSigningConfiguration>> {
+
+    /** {@inheritDoc} */
+    @Override
+    @Nonnull
+    public List<SignatureSigningConfiguration> apply(@Nullable final ProfileRequestContext input) {
+        final List<SignatureSigningConfiguration> configs = new ArrayList<>();
+        final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
+        if (rpc != null) {
+            final ProfileConfiguration pc = rpc.getProfileConfig();
+            if (pc instanceof DefaultDidDocumentConfiguration dpc) {
+                final SignatureSigningConfiguration credential = dpc.getCredentialSignatureSigningConfiguration(input);
+                if (credential != null) {
+                    configs.add(credential);
+                }
+                final SignatureSigningConfiguration statusList = dpc.getStatusListSignatureSigningConfiguration(input);
+                if (statusList != null) {
+                    configs.add(statusList);
+                }
+            }
+        }
+        return configs;
+    }
+}
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundDidDocumentResponseMessage.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundDidDocumentResponseMessage.java
index 47c6902..77a3476 100644
--- a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundDidDocumentResponseMessage.java
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundDidDocumentResponseMessage.java
@@ -22,11 +22,13 @@ import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Function;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 import org.geant.shibboleth.plugin.openidvci.messaging.impl.MetadataDocumentSuccessResponse;
+import org.geant.shibboleth.plugin.openidvci.profile.config.navigate.DidDocumentSignatureSigningConfigurationLookupFunction;
 import org.geant.shibboleth.plugin.openidvci.security.impl.DidSupport;
 import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.action.EventIds;
@@ -40,9 +42,11 @@ import com.nimbusds.jose.jwk.JWK;
 import net.shibboleth.idp.profile.AbstractProfileAction;
 import net.shibboleth.oidc.security.CredentialConversionUtil;
 import net.shibboleth.oidc.security.credential.JWKCredential;
+import net.shibboleth.oidc.security.jose.SignatureSigningConfiguration;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
 /**
@@ -107,18 +111,19 @@ public class FormOutboundDidDocumentResponseMessage extends AbstractProfileActio
     @Nullable
     private String deploymentIssuer;
 
-    /** Credentials issued Verifiable Credentials are signed with. */
+    /** Strategy used to obtain the configurations credentials and Status List Tokens are signed by. */
     @Nonnull
-    private List<Credential> credentialSigningCredentials = CollectionSupport.emptyList();
-
-    /** Credentials Status List Tokens are signed with. */
-    @Nonnull
-    private List<Credential> statusListSigningCredentials = CollectionSupport.emptyList();
+    private Function<ProfileRequestContext, List<SignatureSigningConfiguration>> configurationLookupStrategy;
 
     /** The did:web identifier of this deployment. */
     @NonnullAfterInit
     private String didWeb;
 
+    /** Constructor. */
+    public FormOutboundDidDocumentResponseMessage() {
+        configurationLookupStrategy = new DidDocumentSignatureSigningConfigurationLookupFunction();
+    }
+
     /**
      * Set the url this deployment is reached at.
      *
@@ -130,25 +135,15 @@ public class FormOutboundDidDocumentResponseMessage extends AbstractProfileActio
     }
 
     /**
-     * Set the credentials issued Verifiable Credentials are signed with.
+     * Set the strategy used to obtain the configurations credentials and Status List
+     * Tokens are signed by.
      *
-     * @param credentials the credentials, or null for none
+     * @param strategy lookup strategy
      */
-    public void setCredentialSigningCredentials(@Nullable final List<Credential> credentials) {
+    public void setConfigurationLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, List<SignatureSigningConfiguration>> strategy) {
         checkSetterPreconditions();
-        credentialSigningCredentials = credentials == null ? CollectionSupport.emptyList()
-                : CollectionSupport.copyToList(credentials);
-    }
-
-    /**
-     * Set the credentials Status List Tokens are signed with.
-     *
-     * @param credentials the credentials, or null for none
-     */
-    public void setStatusListSigningCredentials(@Nullable final List<Credential> credentials) {
-        checkSetterPreconditions();
-        statusListSigningCredentials = credentials == null ? CollectionSupport.emptyList()
-                : CollectionSupport.copyToList(credentials);
+        configurationLookupStrategy = Constraint.isNotNull(strategy, "Configuration lookup strategy cannot be null");
     }
 
     /** {@inheritDoc} */
@@ -162,10 +157,6 @@ public class FormOutboundDidDocumentResponseMessage extends AbstractProfileActio
                     "Issuer '" + deploymentIssuer + "' names no host, unable to derive a did:web identifier");
         }
         didWeb = derived;
-
-        if (credentialSigningCredentials.isEmpty() && statusListSigningCredentials.isEmpty()) {
-            throw new ComponentInitializationException("No signing credentials to publish");
-        }
     }
 
     /** {@inheritDoc} */
@@ -176,8 +167,9 @@ public class FormOutboundDidDocumentResponseMessage extends AbstractProfileActio
         final List<Object> assertions = new ArrayList<>();
         final Set<String> stated = new LinkedHashSet<>();
 
-        verificationMethods(credentialSigningCredentials, methods, assertions, stated);
-        verificationMethods(statusListSigningCredentials, methods, assertions, stated);
+        for (final SignatureSigningConfiguration config : configurationLookupStrategy.apply(profileRequestContext)) {
+            verificationMethods(config.getSigningCredentials(), methods, assertions, stated);
+        }
 
         if (methods.isEmpty()) {
             log.error("{} None of the signing keys could be stated, there is no document to publish", getLogPrefix());
@@ -206,8 +198,8 @@ public class FormOutboundDidDocumentResponseMessage extends AbstractProfileActio
     /**
      * State every credential of a list as a verification method of the document.
      *
-     * A credential already stated is left out, the two lists this deployment signs
-     * with are commonly the same key.
+     * A credential already stated is left out, the two configurations this
+     * deployment signs by commonly have the same key.
      *
      * @param credentials credentials to state
      * @param methods     verification methods to write to
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/statuslist/profile/impl/FormStatusListToken.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/statuslist/profile/impl/FormStatusListToken.java
index 6a7fb09..d26d4a1 100644
--- a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/statuslist/profile/impl/FormStatusListToken.java
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/statuslist/profile/impl/FormStatusListToken.java
@@ -19,6 +19,7 @@ package org.geant.shibboleth.plugin.openidvci.statuslist.profile.impl;
 import java.io.IOException;
 import java.time.Duration;
 import java.time.Instant;
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
@@ -36,6 +37,7 @@ import org.geant.shibboleth.plugin.openidvci.statuslist.StatusListIssuerTag;
 import org.geant.shibboleth.plugin.openidvci.statuslist.StatusListParameters;
 import org.geant.shibboleth.plugin.openidvci.statuslist.context.StatusListTokenContext;
 import org.geant.shibboleth.plugin.openidvci.statuslist.profile.StatusListEventIds;
+import org.geant.shibboleth.plugin.openidvci.statuslist.profile.config.navigate.StatusListSignatureSigningConfigurationLookupFunction;
 import org.geant.shibboleth.plugin.openidvci.statuslist.storage.StatusListStore;
 import org.opensaml.messaging.context.MessageContext;
 import org.opensaml.profile.action.ActionSupport;
@@ -51,8 +53,8 @@ import com.nimbusds.jwt.JWTClaimsSet;
 import net.shibboleth.idp.profile.AbstractProfileAction;
 import net.shibboleth.idp.profile.IdPEventIds;
 import net.shibboleth.oidc.security.CredentialConversionUtil;
+import net.shibboleth.oidc.security.jose.SignatureSigningConfiguration;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
@@ -101,9 +103,9 @@ public class FormStatusListToken extends AbstractProfileAction {
     @Nullable
     private String configuredIssuer;
 
-    /** Credentials a Status List Token may be signed with. */
+    /** Strategy used to obtain the configurations Status List Tokens are signed by. */
     @Nonnull
-    private List<Credential> signingCredentials = CollectionSupport.emptyList();
+    private Function<ProfileRequestContext, List<SignatureSigningConfiguration>> configurationLookupStrategy;
 
     /** The value of the request parameter naming the list, tag and all. */
     @Nullable
@@ -116,6 +118,7 @@ public class FormStatusListToken extends AbstractProfileAction {
     /** Constructor. */
     public FormStatusListToken() {
         issuerLookupStrategy = new CredentialIssuerLookupFunction();
+        configurationLookupStrategy = new StatusListSignatureSigningConfigurationLookupFunction();
     }
 
     /**
@@ -189,27 +192,28 @@ public class FormStatusListToken extends AbstractProfileAction {
     }
 
     /**
-     * Set the credentials a Status List Token may be signed with. A list handed out
-     * under a did:jwk issuer names one of them, so every key that has signed such
-     * credentials belongs here while they live.
+     * Set the strategy used to obtain the configurations Status List Tokens are
+     * signed by. A list handed out under a did:jwk issuer names a key of one of them.
      *
-     * @param credentials the credentials, or null for none
+     * @param strategy lookup strategy
      */
-    public void setSigningCredentials(@Nullable final List<Credential> credentials) {
+    public void setConfigurationLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, List<SignatureSigningConfiguration>> strategy) {
         checkSetterPreconditions();
-        signingCredentials = credentials == null ? CollectionSupport.emptyList()
-                : CollectionSupport.copyToList(credentials);
+        configurationLookupStrategy = Constraint.isNotNull(strategy, "Configuration lookup strategy cannot be null");
     }
 
     /**
      * Resolve the issuer a tagged status list is published under.
      *
+     * @param profileRequestContext current profile request context
      * @param tag tag the request carries
      * @return the issuer, or null when the tag names none this deployment answers
      *         for
      */
     @Nullable
-    private String issuerOfTag(@Nonnull final String tag) {
+    private String issuerOfTag(@Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull final String tag) {
 
         if (StatusListIssuerTag.DID_WEB.equals(tag)) {
             return DidSupport.toDidWeb(deploymentIssuer);
@@ -225,7 +229,12 @@ public class FormStatusListToken extends AbstractProfileAction {
             return null;
         }
 
-        for (final Credential candidate : signingCredentials) {
+        final List<Credential> candidates = new ArrayList<>();
+        for (final SignatureSigningConfiguration config : configurationLookupStrategy.apply(profileRequestContext)) {
+            candidates.addAll(config.getSigningCredentials());
+        }
+
+        for (final Credential candidate : candidates) {
             final JWK key = CredentialConversionUtil.credentialToKey(candidate);
             if (key == null) {
                 continue;
@@ -279,7 +288,8 @@ public class FormStatusListToken extends AbstractProfileAction {
         }
 
         final String tag = StatusListIssuerTag.tagOf(reference);
-        issuerId = tag == null ? issuerLookupStrategy.apply(profileRequestContext) : issuerOfTag(tag);
+        issuerId = tag == null ? issuerLookupStrategy.apply(profileRequestContext)
+                : issuerOfTag(profileRequestContext, tag);
         if (issuerId == null) {
             log.error("{} Unable to determine issuer of the status list token", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/statuslist/profile/impl/SignStatusListToken.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/statuslist/profile/impl/SignStatusListToken.java
index cb17ca0..8c0067c 100644
--- a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/statuslist/profile/impl/SignStatusListToken.java
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/statuslist/profile/impl/SignStatusListToken.java
@@ -30,6 +30,7 @@ import org.geant.shibboleth.plugin.openidvci.cose.impl.CoseSign1Signer;
 import org.geant.shibboleth.plugin.openidvci.security.impl.CertificateChainJWSTokenSigner;
 import org.geant.shibboleth.plugin.openidvci.security.impl.DidSupport;
 import org.geant.shibboleth.plugin.openidvci.statuslist.context.StatusListTokenContext;
+import org.geant.shibboleth.plugin.openidvci.statuslist.profile.config.navigate.StatusListSignatureSigningConfigurationLookupFunction;
 import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.action.EventIds;
@@ -45,12 +46,12 @@ import com.nimbusds.jose.jwk.JWK;
 import net.shibboleth.idp.profile.AbstractProfileAction;
 import net.shibboleth.oidc.security.CredentialConversionUtil;
 import net.shibboleth.oidc.security.jose.SignatureException;
+import net.shibboleth.oidc.security.jose.SignatureSigningConfiguration;
 import net.shibboleth.oidc.security.jose.SignatureSigningParameters;
 import net.shibboleth.oidc.security.jose.context.SecurityParametersContext;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
 import net.shibboleth.shared.codec.EncodingException;
-import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.logic.PredicateSupport;
 import net.shibboleth.shared.primitive.LoggerFactory;
@@ -69,9 +70,9 @@ public class SignStatusListToken extends AbstractProfileAction {
     @Nonnull
     private static final String TOKEN_TYPE = "statuslist+jwt";
 
-    /** Credentials a Status List Token may be signed with. */
+    /** Strategy used to obtain the configurations Status List Tokens are signed by. */
     @Nonnull
-    private List<Credential> signingCredentials = CollectionSupport.emptyList();
+    private Function<ProfileRequestContext, List<SignatureSigningConfiguration>> configurationLookupStrategy;
 
     /** Class logger. */
     @Nonnull
@@ -111,6 +112,8 @@ public class SignStatusListToken extends AbstractProfileAction {
                 StatusListTokenContext.class).compose(new OutboundMessageContextLookup());
         assert tlcs != null;
         statusListTokenContextLookupStrategy = tlcs;
+
+        configurationLookupStrategy = new StatusListSignatureSigningConfigurationLookupFunction();
     }
 
     /**
@@ -191,28 +194,29 @@ public class SignStatusListToken extends AbstractProfileAction {
     }
 
     /**
-     * Set the credentials a Status List Token may be signed with, searched when the
-     * issuer names one key.
+     * Set the strategy used to obtain the configurations Status List Tokens are
+     * signed by, searched when the issuer names one key.
      *
-     * @param credentials the credentials, or null for none
+     * @param strategy lookup strategy
      */
-    public void setSigningCredentials(@Nullable final List<Credential> credentials) {
+    public void setConfigurationLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, List<SignatureSigningConfiguration>> strategy) {
         checkSetterPreconditions();
-        signingCredentials = credentials == null ? CollectionSupport.emptyList()
-                : CollectionSupport.copyToList(credentials);
+        configurationLookupStrategy = Constraint.isNotNull(strategy, "Configuration lookup strategy cannot be null");
     }
 
     /**
      * Take the parameters signing with the key a thumbprint names.
      *
+     * @param profileRequestContext current profile request context
      * @param parameters parameters resolved by the signing configuration
      * @param thumbprint thumbprint of the required key, or null when any key does
      * @return the parameters to sign with, or null when the required key is not
      *         among the ones this deployment signs with
      */
     @Nullable
-    private SignatureSigningParameters withRequiredKey(@Nonnull final SignatureSigningParameters parameters,
-            @Nullable final String thumbprint) {
+    private SignatureSigningParameters withRequiredKey(@Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull final SignatureSigningParameters parameters, @Nullable final String thumbprint) {
 
         if (thumbprint == null) {
             return parameters;
@@ -221,12 +225,14 @@ public class SignStatusListToken extends AbstractProfileAction {
             return parameters;
         }
 
-        for (final Credential candidate : signingCredentials) {
-            if (thumbprint.equals(thumbprintOf(candidate))) {
-                final SignatureSigningParameters required = new SignatureSigningParameters();
-                required.setSignatureAlgorithm(parameters.getSignatureAlgorithm());
-                required.setSigningCredential(candidate);
-                return required;
+        for (final SignatureSigningConfiguration config : configurationLookupStrategy.apply(profileRequestContext)) {
+            for (final Credential candidate : config.getSigningCredentials()) {
+                if (thumbprint.equals(thumbprintOf(candidate))) {
+                    final SignatureSigningParameters required = new SignatureSigningParameters();
+                    required.setSignatureAlgorithm(parameters.getSignatureAlgorithm());
+                    required.setSigningCredential(candidate);
+                    return required;
+                }
             }
         }
         return null;
@@ -260,8 +266,8 @@ public class SignStatusListToken extends AbstractProfileAction {
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
 
-        final SignatureSigningParameters parameters = withRequiredKey(signatureSigningParameters,
-                ctx.getKeyThumbprint());
+        final SignatureSigningParameters parameters = withRequiredKey(profileRequestContext,
+                signatureSigningParameters, ctx.getKeyThumbprint());
         if (parameters == null) {
             log.error("{} The key the issuer of this status list names is not among the ones Status List Tokens "
                     + "are signed with", getLogPrefix());
diff --git a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/did/document/document-beans.xml b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/did/document/document-beans.xml
index 6e0ce45..2fb61f1 100644
--- a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/did/document/document-beans.xml
+++ b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/did/document/document-beans.xml
@@ -22,9 +22,7 @@
   <bean id="FormOutboundMessage"
         class="org.geant.shibboleth.plugin.openidvci.profile.impl.FormOutboundDidDocumentResponseMessage"
         scope="prototype"
-        p:deploymentIssuer="#{getObject('shibboleth.oidc.issuer')}"
-        p:credentialSigningCredentials="#{getObject('openidvci.SigningCredentials') ?: getObject('shibboleth.oidc.SigningCredentialsFactory')}"
-        p:statusListSigningCredentials="#{getObject('openidvci.status-list.SigningCredentials') ?: getObject('openidvci.SigningCredentials') ?: getObject('shibboleth.oidc.SigningCredentialsFactory')}" />
+        p:deploymentIssuer="#{getObject('shibboleth.oidc.issuer')}" />
 
   <bean id="BuildErrorResponseFromEvent" class="net.shibboleth.oidc.profile.impl.BuildJSONErrorResponseFromEvent"
         scope="prototype" p:defaultStatusCode="500" p:defaultCode="server_error"
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 031f0ef..4813571 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
@@ -55,13 +55,11 @@
         p:issuerLookupStrategy-ref="openidvci.IssuerLookupStrategy"
         p:deploymentIssuer="#{getObject('shibboleth.oidc.issuer')}"
         p:configuredIssuer="%{openidvci.issuer:}"
-        p:signingCredentials="#{getObject('openidvci.status-list.SigningCredentials') ?: getObject('openidvci.SigningCredentials') ?: getObject('shibboleth.oidc.SigningCredentialsFactory')}"
         p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier" />
 
   <bean id="SignStatusListToken"
         class="org.geant.shibboleth.plugin.openidvci.statuslist.profile.impl.SignStatusListToken"
-        scope="prototype"
-        p:signingCredentials="#{getObject('openidvci.status-list.SigningCredentials') ?: getObject('openidvci.SigningCredentials') ?: getObject('shibboleth.oidc.SigningCredentialsFactory')}">
+        scope="prototype">
     <property name="cwtCondition">
       <bean class="org.geant.shibboleth.plugin.openidvci.profile.logic.StatusListCwtRequestedPredicate"
             c:supplier-ref="shibboleth.HttpServletRequestSupplier" />
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 58a39db..caf371b 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
@@ -80,9 +80,10 @@
     resolves a credential of a did:web issuer by. Serves anyone, as the two above do.
     -->
     <bean id="DID.Document" lazy-init="true"
-        class="org.geant.shibboleth.plugin.openidvci.profile.config.impl.DefaultMetadataDocumentConfiguration"
-        c:id="#{T(org.geant.shibboleth.plugin.openidvci.profile.config.impl.DefaultMetadataDocumentConfiguration).PROFILE_ID_DID_DOCUMENT}"
-        p:securityConfiguration-ref="%{idp.security.oidc.config:shibboleth.oidc.DefaultSecurityConfiguration}" />
+        class="org.geant.shibboleth.plugin.openidvci.profile.config.impl.DefaultDidDocumentConfiguration"
+        p:securityConfiguration-ref="%{idp.security.oidc.config:shibboleth.oidc.DefaultSecurityConfiguration}"
+        p:credentialSignatureSigningConfiguration="#{getObject('openidvci.SigningConfiguration')}"
+        p:statusListSignatureSigningConfiguration="#{getObject('openidvci.status-list.SigningConfiguration')}" />
 
     <bean id="OAuth.StatusList" lazy-init="true"
         class="org.geant.shibboleth.plugin.openidvci.statuslist.profile.config.impl.DefaultStatusListConfiguration"

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


More information about the commits mailing list