[java-idp-plugin-vci] branch dev/DID_WEB_REVIEW updated: did:web document: warn when two signing keys have same name, javadoc improvements

Codeberg noreply at shibboleth.net
Fri Nov 6 09:16:05 UTC 2026


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

codeberg pushed a commit to branch dev/DID_WEB_REVIEW
in repository java-idp-plugin-vci.

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

The following commit(s) were added to refs/heads/dev/DID_WEB_REVIEW by this push:
     new 77a6fba  did:web document: warn when two signing keys have same name, javadoc improvements
77a6fba is described below

commit 77a6fbacba031001dade478a8a62d6b0d63f5495
Author: Janne Lauros <janne.lauros at csc.fi>
AuthorDate: Fri Nov 6 11:15:22 2026 +0200

    did:web document: warn when two signing keys have same name, javadoc improvements
---
 .../FormOutboundDidDocumentResponseMessage.java    | 38 +++++++++++++---------
 ...FormOutboundDidDocumentResponseMessageTest.java | 17 ++++++++++
 2 files changed, 39 insertions(+), 16 deletions(-)

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 957cb07..d62c673 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
@@ -18,10 +18,8 @@ package org.geant.shibboleth.plugin.openidvci.profile.impl;
 
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
-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;
@@ -38,7 +36,9 @@ import org.opensaml.security.credential.Credential;
 import org.slf4j.Logger;
 
 import com.google.common.net.HttpHeaders;
+import com.nimbusds.jose.JOSEException;
 import com.nimbusds.jose.jwk.JWK;
+import com.nimbusds.jose.util.Base64URL;
 
 import net.minidev.json.JSONObject;
 import net.shibboleth.idp.profile.AbstractProfileAction;
@@ -56,11 +56,8 @@ import net.shibboleth.shared.primitive.LoggerFactory;
 import net.shibboleth.shared.primitive.StringSupport;
 
 /**
- * Action publishing the did:web document of this deployment.
- *
- * States the public half of every key credentials and Status List Tokens are
- * signed with. Formed from the signing credentials rather than read from a
- * file, so a key is published for as long as it is among them.
+ * Action publishing the did:web document of this deployment from the keys credentials and Status List Tokens are
+ * signed with.
  */
 public class FormOutboundDidDocumentResponseMessage extends AbstractProfileAction {
 
@@ -187,7 +184,7 @@ public class FormOutboundDidDocumentResponseMessage extends AbstractProfileActio
 
         final List<Object> methods = new ArrayList<>();
         final List<Object> assertions = new ArrayList<>();
-        final Set<String> stated = new LinkedHashSet<>();
+        final Map<String, Base64URL> stated = new LinkedHashMap<>();
 
         for (final SignatureSigningConfiguration config : configurationLookupStrategy.apply(profileRequestContext)) {
             verificationMethods(config.getSigningCredentials(), methods, assertions, stated);
@@ -219,22 +216,20 @@ public class FormOutboundDidDocumentResponseMessage extends AbstractProfileActio
         profileRequestContext.ensureOutboundMessageContext()
                 .setMessage(new JSONSuccessResponse(new JSONObject(document)));
 
-        log.debug("{} Publishing the did:web document of {}, verification methods {}", getLogPrefix(), didWeb, stated);
+        log.debug("{} Publishing the did:web document of {}, verification methods {}", getLogPrefix(), didWeb,
+                stated.keySet());
     }
 
     /**
      * State every credential of a list as a verification method of the document.
      *
-     * 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
      * @param assertions  assertion methods to write to
-     * @param stated      verification methods already stated
+     * @param stated      thumbprints of the verification methods already stated
      */
     private void verificationMethods(@Nonnull final List<Credential> credentials, @Nonnull final List<Object> methods,
-            @Nonnull final List<Object> assertions, @Nonnull final Set<String> stated) {
+            @Nonnull final List<Object> assertions, @Nonnull final Map<String, Base64URL> stated) {
 
         for (final Credential credential : credentials) {
 
@@ -244,15 +239,26 @@ public class FormOutboundDidDocumentResponseMessage extends AbstractProfileActio
                 continue;
             }
 
-            // Conversion keeps the public components only.
             final JWK key = CredentialConversionUtil.credentialToKey(credential);
             if (key == null) {
                 log.warn("Signing key '{}' is neither RSA nor EC, leaving it out of the did:web document", keyName);
                 continue;
             }
 
+            final Base64URL thumbprint;
+            try {
+                thumbprint = key.computeThumbprint();
+            } catch (final JOSEException e) {
+                log.warn("Signing key '{}' has no thumbprint, leaving it out of the did:web document", keyName);
+                continue;
+            }
+
             final String id = didWeb + "#" + keyName;
-            if (!stated.add(id)) {
+            final Base64URL first = stated.putIfAbsent(id, thumbprint);
+            if (first != null) {
+                if (!first.equals(thumbprint)) {
+                    log.warn("Signing keys named '{}' differ, only the first is in the did:web document", keyName);
+                }
                 continue;
             }
 
diff --git a/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundDidDocumentResponseMessageTest.java b/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundDidDocumentResponseMessageTest.java
index 2c8c08c..52e5b95 100644
--- a/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundDidDocumentResponseMessageTest.java
+++ b/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundDidDocumentResponseMessageTest.java
@@ -46,6 +46,7 @@ import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileR
 import net.shibboleth.idp.profile.testing.ActionTestingSupport;
 import net.shibboleth.idp.profile.testing.RequestContextBuilder;
 import net.shibboleth.oidc.profile.messaging.JSONSuccessResponse;
+import net.shibboleth.oidc.security.CredentialConversionUtil;
 import net.shibboleth.oidc.security.credential.BasicJWKCredential;
 import net.shibboleth.oidc.security.jose.SignatureSigningConfiguration;
 import net.shibboleth.oidc.security.jose.impl.BasicSignatureSigningConfiguration;
@@ -178,6 +179,22 @@ public class FormOutboundDidDocumentResponseMessageTest {
         Assert.assertEquals(published().get("assertionMethod"), List.of(DID + "#signingvci-x509"));
     }
 
+    @Test
+    public void testDifferentKeysSameNameStatedOnce() throws Exception {
+        final Credential renamedCopy = x509Like("signingvci-x509");
+        final FormOutboundDidDocumentResponseMessage action = actionOf("https://issuer.example.org",
+                List.of(configOf(credentialKey), configOf(renamedCopy)), true);
+        action.initialize();
+
+        ActionTestingSupport.assertProceedEvent(action.execute(requestCtx));
+
+        final JSONArray methods = (JSONArray) published().get("verificationMethod");
+        Assert.assertEquals(methods.size(), 1);
+        final Map<?, ?> key = (Map<?, ?>) ((Map<?, ?>) methods.get(0)).get("publicKeyJwk");
+        Assert.assertEquals(key.get("x"),
+                ((ECKey) CredentialConversionUtil.credentialToKey(credentialKey)).getX().toString());
+    }
+
     @Test
     public void testDefaultHeaders() throws Exception {
         final FormOutboundDidDocumentResponseMessage action =

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


More information about the commits mailing list