[java-idp-plugin-vci] 01/02: Credential Issuer metadata, warn when signed metadata has no certificate chain

Codeberg noreply at shibboleth.net
Fri Nov 6 12:16:28 UTC 2026


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

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

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

commit f3fc0bcd6d96325ef8146f153944dc87c3d3d831
Author: Janne Lauros <janne.lauros at csc.fi>
AuthorDate: Fri Nov 6 13:52:16 2026 +0200

    Credential Issuer metadata, warn when signed metadata has no certificate chain
---
 .../FormOutboundIssuerMetadataResponseMessage.java | 12 +++++--
 ...mOutboundIssuerMetadataResponseMessageTest.java | 39 ++++++++++++++++++++++
 2 files changed, 49 insertions(+), 2 deletions(-)

diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundIssuerMetadataResponseMessage.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundIssuerMetadataResponseMessage.java
index ec6b380..6c248a4 100644
--- a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundIssuerMetadataResponseMessage.java
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundIssuerMetadataResponseMessage.java
@@ -49,7 +49,6 @@ import net.shibboleth.oidc.security.jose.SignatureException;
 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.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.logic.Constraint;
@@ -97,7 +96,7 @@ public class FormOutboundIssuerMetadataResponseMessage extends AbstractProfileAc
     private Duration signedMetadataLifetime;
 
     /** Metadata to publish. */
-    @NonnullBeforeExec
+    @Nullable
     private CredentialIssuerMetadata metadata;
 
     /** Value of the Cache-Control header. */
@@ -199,6 +198,8 @@ public class FormOutboundIssuerMetadataResponseMessage extends AbstractProfileAc
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
 
+        assert metadata != null;
+
         final HttpServletResponse response = getHttpServletResponse();
         if (response == null) {
             log.error("{} Could not fetch HttpServletResponse", getLogPrefix());
@@ -241,6 +242,8 @@ public class FormOutboundIssuerMetadataResponseMessage extends AbstractProfileAc
     private void publishSigned(@Nonnull final ProfileRequestContext profileRequestContext,
             @Nonnull final SignatureSigningParameters parameters, @Nonnull final HttpServletResponse response) {
 
+        assert metadata != null;
+
         final Instant now = Instant.now();
         final JWTClaimsSet.Builder claims = new JWTClaimsSet.Builder();
         metadata.getMembers().forEach(claims::claim);
@@ -260,6 +263,10 @@ public class FormOutboundIssuerMetadataResponseMessage extends AbstractProfileAc
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_SEC_CFG);
             return;
         }
+        if (document.getHeader().getX509CertChain() == null) {
+            log.warn("{} Signed metadata carries no certificate chain, wallets cannot verify its signer",
+                    getLogPrefix());
+        }
 
         setHeaders(response);
         profileRequestContext.ensureOutboundMessageContext()
@@ -350,6 +357,7 @@ public class FormOutboundIssuerMetadataResponseMessage extends AbstractProfileAc
      */
     private boolean hasMember(@Nonnull final ProfileRequestContext profileRequestContext,
             @Nonnull final String member) {
+        assert metadata != null;
         if (metadata.hasMember(member)) {
             return true;
         }
diff --git a/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundIssuerMetadataResponseMessageTest.java b/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundIssuerMetadataResponseMessageTest.java
index f5cacb1..86e3365 100644
--- a/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundIssuerMetadataResponseMessageTest.java
+++ b/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundIssuerMetadataResponseMessageTest.java
@@ -16,8 +16,11 @@
 
 package org.geant.shibboleth.plugin.openidvci.profile.impl;
 
+import java.io.InputStream;
 import java.security.KeyPairGenerator;
 import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.ECPublicKey;
 import java.time.Duration;
 import java.time.Instant;
 import java.time.temporal.ChronoUnit;
@@ -33,6 +36,9 @@ import org.geant.shibboleth.plugin.openidvci.metadata.resolver.CredentialIssuerM
 import org.opensaml.messaging.context.MessageContext;
 import org.opensaml.profile.action.EventIds;
 import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.security.crypto.KeySupport;
+import org.opensaml.security.x509.BasicX509Credential;
+import org.opensaml.security.x509.X509Support;
 import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.DataProvider;
@@ -155,6 +161,23 @@ public class FormOutboundIssuerMetadataResponseMessageTest {
                 .setSignatureSigningParameters(parameters);
     }
 
+    private X509Certificate configureX509Signing() throws Exception {
+        final X509Certificate certificate;
+        try (InputStream in = getClass().getResourceAsStream("/credentials/x5c-test-cert.pem")) {
+            certificate = X509Support.decodeCertificate(in.readAllBytes());
+        }
+        final PrivateKey privateKey;
+        try (InputStream in = getClass().getResourceAsStream("/credentials/x5c-test-key.pem")) {
+            privateKey = KeySupport.decodePrivateKey(in.readAllBytes(), null);
+        }
+        final SignatureSigningParameters parameters = new SignatureSigningParameters();
+        parameters.setSigningCredential(new BasicX509Credential(certificate, privateKey));
+        parameters.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_ES_256);
+        profileRequestCtx.ensureOutboundMessageContext().ensureSubcontext(SecurityParametersContext.class)
+                .setSignatureSigningParameters(parameters);
+        return certificate;
+    }
+
     private SignedJWT published() throws Exception {
         final Object message = profileRequestCtx.ensureOutboundMessageContext().getMessage();
         Assert.assertTrue(message instanceof SignedCredentialIssuerMetadataSuccessResponse);
@@ -313,6 +336,22 @@ public class FormOutboundIssuerMetadataResponseMessageTest {
         }
     }
 
+    @Test
+    public void testSignedWithCertificateChain() throws Exception {
+        httpRequest.addHeader("Accept", "application/jwt");
+        final X509Certificate certificate = configureX509Signing();
+
+        ActionTestingSupport.assertProceedEvent(
+                actionOf(CredentialIssuerMetadata.parse(members())).execute(requestCtx));
+        final SignedJWT document = published();
+
+        Assert.assertNotNull(document.getHeader().getX509CertChain());
+        Assert.assertEquals(document.getHeader().getX509CertChain().get(0).decode(), certificate.getEncoded());
+        Assert.assertNull(document.getHeader().getKeyID());
+        Assert.assertTrue(document.verify(new ECDSAVerifier(
+                (ECPublicKey) certificate.getPublicKey())));
+    }
+
     @Test
     public void testSignedDocumentLifetime() throws Exception {
         httpRequest.addHeader("Accept", "application/jwt");

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


More information about the commits mailing list