Restructure credential_configurations_supported parsing to handle credential_metadata with display and claims && Update Credential Issuer Metadata structure (#42001)

Closes #41587
Closes #41597

Signed-off-by: forkimenjeckayang <forkimenjeckayang@gmail.com>
This commit is contained in:
forkimenjeckayang 2025-09-04 13:48:56 +01:00 committed by GitHub
parent 320ea5a9a7
commit d5feb76f1f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 426 additions and 74 deletions

View File

@ -32,15 +32,20 @@ import org.keycloak.protocol.oid4vc.issuance.credentialbuilder.CredentialBuilder
import org.keycloak.protocol.oid4vc.model.CredentialIssuer;
import org.keycloak.protocol.oid4vc.model.CredentialResponseEncryptionMetadata;
import org.keycloak.protocol.oid4vc.model.CredentialRequestEncryptionMetadata;
import org.keycloak.protocol.oid4vc.model.SupportedCredentialConfiguration;
import org.keycloak.protocol.oidc.utils.JWKSServerUtils;
import org.keycloak.services.Urls;
import org.keycloak.urls.UrlType;
import org.keycloak.wellknown.WellKnownProvider;
import org.jboss.logging.Logger;
import org.keycloak.jose.jwk.JWK;
import org.keycloak.jose.jwk.JSONWebKeySet;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.keycloak.crypto.KeyType.RSA;
@ -81,8 +86,8 @@ public class OID4VCIssuerWellKnownProvider implements WellKnownProvider {
.setCredentialsSupported(getSupportedCredentials(keycloakSession))
.setAuthorizationServers(List.of(getIssuer(context)))
.setCredentialResponseEncryption(getCredentialResponseEncryption(keycloakSession))
.setBatchCredentialIssuance(getBatchCredentialIssuance(keycloakSession))
.setSignedMetadata(getSignedMetadata(keycloakSession));
.setCredentialRequestEncryption(getCredentialRequestEncryption(keycloakSession))
.setBatchCredentialIssuance(getBatchCredentialIssuance(keycloakSession));
return issuer;
}
@ -119,11 +124,6 @@ public class OID4VCIssuerWellKnownProvider implements WellKnownProvider {
return null;
}
private String getSignedMetadata(KeycloakSession session) {
RealmModel realm = session.getContext().getRealm();
return realm.getAttribute("signed_metadata");
}
/**
* Returns the credential response encryption высоко for the issuer.
* Now determines supported algorithms from available realm keys.
@ -138,6 +138,27 @@ public class OID4VCIssuerWellKnownProvider implements WellKnownProvider {
// Get supported algorithms from available encryption keys
metadata.setAlgValuesSupported(getSupportedEncryptionAlgorithms(session));
metadata.setEncValuesSupported(getSupportedEncryptionMethods());
metadata.setZipValuesSupported(getSupportedCompressionMethods());
metadata.setEncryptionRequired(isEncryptionRequired(realm));
return metadata;
}
/**
* Returns the credential request encryption metadata for the issuer.
* Determines supported algorithms from available realm keys.
*
* @param session The Keycloak session
* @return The credential request encryption metadata
*/
public static CredentialRequestEncryptionMetadata getCredentialRequestEncryption(KeycloakSession session) {
RealmModel realm = session.getContext().getRealm();
CredentialRequestEncryptionMetadata metadata = new CredentialRequestEncryptionMetadata();
// Get supported algorithms from available encryption keys
metadata.setJwks(getEncryptionJwks(session));
metadata.setEncValuesSupported(getSupportedEncryptionMethods());
metadata.setZipValuesSupported(getSupportedCompressionMethods());
metadata.setEncryptionRequired(isEncryptionRequired(realm));
return metadata;
@ -178,6 +199,37 @@ public class OID4VCIssuerWellKnownProvider implements WellKnownProvider {
return List.of(JWEConstants.A256GCM);
}
/**
* Returns the supported compression methods from realm attributes.
*
* Note: Keycloak's JWE implementation currently only has placeholder support for compression
* in the JWEHeader class, but no actual compression/decompression logic is implemented.
* The compression algorithm field exists but is not processed during JWE encoding/decoding.
*
* TODO: Implement JWE compression support when Keycloak core adds compression functionality
*/
private static List<String> getSupportedCompressionMethods() {
// Keycloak JWE implementation lacks compression support - only header placeholder exists
return List.of();
}
/**
* Returns the encryption JWKS from realm keys.
* Filters the realm JWKS to include only encryption keys.
*/
private static List<JWK> getEncryptionJwks(KeycloakSession session) {
RealmModel realm = session.getContext().getRealm();
JSONWebKeySet realmJwks = JWKSServerUtils.getRealmJwks(session, realm);
if (realmJwks.getKeys() == null) {
return List.of();
}
return Stream.of(realmJwks.getKeys())
.filter(jwk -> KeyUse.ENC.getSpecName().equals(jwk.getPublicKeyUse()))
.toList();
}
/**
* Returns whether encryption is required from realm attributes.
*/

View File

@ -55,9 +55,6 @@ public class CredentialIssuer {
@JsonProperty("batch_credential_issuance")
private BatchCredentialIssuance batchCredentialIssuance;
@JsonProperty("signed_metadata")
private String signedMetadata;
@JsonProperty("credential_configurations_supported")
private Map<String, SupportedCredentialConfiguration> credentialsSupported;
@ -67,6 +64,9 @@ public class CredentialIssuer {
@JsonProperty("credential_response_encryption")
private CredentialResponseEncryptionMetadata credentialResponseEncryption;
@JsonProperty("credential_request_encryption")
private CredentialRequestEncryptionMetadata credentialRequestEncryption;
public String getCredentialIssuer() {
return credentialIssuer;
}
@ -130,15 +130,6 @@ public class CredentialIssuer {
return this;
}
public String getSignedMetadata() {
return signedMetadata;
}
public CredentialIssuer setSignedMetadata(String signedMetadata) {
this.signedMetadata = signedMetadata;
return this;
}
public Map<String, SupportedCredentialConfiguration> getCredentialsSupported() {
return credentialsSupported;
}
@ -169,6 +160,15 @@ public class CredentialIssuer {
return this;
}
public CredentialRequestEncryptionMetadata getCredentialRequestEncryption() {
return credentialRequestEncryption;
}
public CredentialIssuer setCredentialRequestEncryption(CredentialRequestEncryptionMetadata credentialRequestEncryption) {
this.credentialRequestEncryption = credentialRequestEncryption;
return this;
}
/**
* Represents the batch_credential_issuance metadata parameter.
*/

View File

@ -0,0 +1,92 @@
/*
* Copyright 2025 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.keycloak.protocol.oid4vc.model;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.keycloak.models.oid4vci.CredentialScopeModel;
import org.keycloak.models.KeycloakSession;
import java.util.List;
/**
* Represents credential_metadata as defined in the OID4VCI specification.
* Contains information relevant to the usage and display of issued Credentials.
* Format-specific mechanisms can overwrite the information in this object.
*
* @author <a href="https://github.com/forkimenjeckayang">Forkim Akwichek</a>
* @see https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-16.html#name-credential-issuer-metadata-p
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class CredentialMetadata {
@JsonProperty("display")
private List<DisplayObject> display;
@JsonProperty("claims")
private Claims claims;
/**
* Parse credential metadata from a credential scope model.
* Format-specific mechanisms (like SD-JWT VC display metadata) are always preferred by the Wallet
* over the information in this object, which serves as the default fallback.
*
* @param keycloakSession The Keycloak session
* @param credentialScope The credential scope model
* @return The parsed credential metadata, or null if no metadata is available
*/
public static CredentialMetadata parse(KeycloakSession keycloakSession, CredentialScopeModel credentialScope) {
CredentialMetadata metadata = new CredentialMetadata();
// Parse format-specific display metadata (prioritized)
List<DisplayObject> formatSpecificDisplay = DisplayObject.parse(credentialScope);
if (formatSpecificDisplay != null && !formatSpecificDisplay.isEmpty()) {
metadata.setDisplay(formatSpecificDisplay);
}
// Parse format-specific claims metadata (prioritized)
Claims formatSpecificClaims = Claims.parse(keycloakSession, credentialScope);
if (formatSpecificClaims != null && !formatSpecificClaims.isEmpty()) {
metadata.setClaims(formatSpecificClaims);
}
// Only return metadata if we have some content
if (metadata.getDisplay() != null || metadata.getClaims() != null) {
return metadata;
}
return null;
}
public List<DisplayObject> getDisplay() {
return display;
}
public CredentialMetadata setDisplay(List<DisplayObject> display) {
this.display = display;
return this;
}
public Claims getClaims() {
return claims;
}
public CredentialMetadata setClaims(Claims claims) {
this.claims = claims;
return this;
}
}

View File

@ -0,0 +1,82 @@
/*
* Copyright 2025 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.keycloak.protocol.oid4vc.model;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.keycloak.jose.jwk.JWK;
import java.util.List;
/**
* Represents the credential_request_encryption metadata for an OID4VCI Credential Issuer.
* {@see https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-16.html#name-credential-issuer-metadata-p}
*
* @author <a href="https://github.com/forkimenjeckayang">Forkim Akwichek</a>
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class CredentialRequestEncryptionMetadata {
@JsonProperty("jwks")
private List<JWK> jwks;
@JsonProperty("enc_values_supported")
private List<String> encValuesSupported;
@JsonProperty("zip_values_supported")
private List<String> zipValuesSupported;
@JsonProperty("encryption_required")
private Boolean encryptionRequired;
public List<JWK> getJwks() {
return jwks;
}
public CredentialRequestEncryptionMetadata setJwks(List<JWK> jwks) {
this.jwks = jwks;
return this;
}
public List<String> getEncValuesSupported() {
return encValuesSupported;
}
public CredentialRequestEncryptionMetadata setEncValuesSupported(List<String> encValuesSupported) {
this.encValuesSupported = encValuesSupported;
return this;
}
public List<String> getZipValuesSupported() {
return zipValuesSupported;
}
public CredentialRequestEncryptionMetadata setZipValuesSupported(List<String> zipValuesSupported) {
this.zipValuesSupported = zipValuesSupported;
return this;
}
public Boolean getEncryptionRequired() {
return encryptionRequired;
}
public CredentialRequestEncryptionMetadata setEncryptionRequired(Boolean encryptionRequired) {
this.encryptionRequired = encryptionRequired;
return this;
}
}

View File

@ -24,7 +24,7 @@ import java.util.List;
/**
* Represents the credential_response_encryption metadata for an OID4VCI Credential Issuer.
* {@see https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-15.html#name-credential-issuer-metadata-p}
* {@see https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-16.html#name-credential-issuer-metadata-p}
*
* @author <a href="mailto:Bertrand.Ogen@adorsys.com">Bertrand Ogen</a>
*/
@ -37,6 +37,9 @@ public class CredentialResponseEncryptionMetadata {
@JsonProperty("enc_values_supported")
private List<String> encValuesSupported;
@JsonProperty("zip_values_supported")
private List<String> zipValuesSupported;
@JsonProperty("encryption_required")
private Boolean encryptionRequired;
@ -56,6 +59,14 @@ public class CredentialResponseEncryptionMetadata {
this.encValuesSupported = encValuesSupported;
}
public List<String> getZipValuesSupported() {
return zipValuesSupported;
}
public void setZipValuesSupported(List<String> zipValuesSupported) {
this.zipValuesSupported = zipValuesSupported;
}
public Boolean getEncryptionRequired() {
return encryptionRequired;
}

View File

@ -30,6 +30,7 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
/**
* Represents a DisplayObject, as used in the OID4VCI Credentials Issuer Metadata
@ -59,6 +60,8 @@ public class DisplayObject {
private static final String BG_COLOR_KEY = "background_color";
@JsonIgnore
private static final String TEXT_COLOR_KEY = "text_color";
@JsonIgnore
private static final String BG_IMAGE_KEY = "background_image";
@JsonProperty(DisplayObject.NAME_KEY)
private String name;
@ -67,7 +70,7 @@ public class DisplayObject {
private String locale;
@JsonProperty(DisplayObject.LOGO_KEY)
private String logo;
private LogoObject logo;
@JsonProperty(DisplayObject.DESCRIPTION_KEY)
private String description;
@ -78,6 +81,9 @@ public class DisplayObject {
@JsonProperty(DisplayObject.TEXT_COLOR_KEY)
private String textColor;
@JsonProperty(DisplayObject.BG_IMAGE_KEY)
private BackgroundImageObject backgroundImage;
public static List<DisplayObject> parse(CredentialScopeModel credentialScope) {
String display = credentialScope.getVcDisplay();
if (StringUtil.isBlank(display)) {
@ -114,11 +120,11 @@ public class DisplayObject {
return this;
}
public String getLogo() {
public LogoObject getLogo() {
return logo;
}
public DisplayObject setLogo(String logo) {
public DisplayObject setLogo(LogoObject logo) {
this.logo = logo;
return this;
}
@ -150,6 +156,15 @@ public class DisplayObject {
return this;
}
public BackgroundImageObject getBackgroundImage() {
return backgroundImage;
}
public DisplayObject setBackgroundImage(BackgroundImageObject backgroundImage) {
this.backgroundImage = backgroundImage;
return this;
}
public String toJsonString(){
try {
return JsonSerialization.writeValueAsString(this);
@ -178,6 +193,8 @@ public class DisplayObject {
return false;
if (getBackgroundColor() != null ? !getBackgroundColor().equals(that.getBackgroundColor()) : that.getBackgroundColor() != null)
return false;
if (getBackgroundImage() != null ? !getBackgroundImage().equals(that.getBackgroundImage()) : that.getBackgroundImage() != null)
return false;
return getTextColor() != null ? getTextColor().equals(that.getTextColor()) : that.getTextColor() == null;
}
@ -188,6 +205,7 @@ public class DisplayObject {
result = 31 * result + (getLogo() != null ? getLogo().hashCode() : 0);
result = 31 * result + (getDescription() != null ? getDescription().hashCode() : 0);
result = 31 * result + (getBackgroundColor() != null ? getBackgroundColor().hashCode() : 0);
result = 31 * result + (getBackgroundImage() != null ? getBackgroundImage().hashCode() : 0);
result = 31 * result + (getTextColor() != null ? getTextColor().hashCode() : 0);
return result;
}
@ -200,4 +218,78 @@ public class DisplayObject {
throw new RuntimeException(e);
}
}
/**
* Represents a logo object as defined in the OID4VCI specification.
* {@see https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-issuer-metadata-p}
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class LogoObject {
@JsonProperty("uri")
private String uri;
@JsonProperty("alt_text")
private String altText;
public String getUri() {
return uri;
}
public LogoObject setUri(String uri) {
this.uri = uri;
return this;
}
public String getAltText() {
return altText;
}
public LogoObject setAltText(String altText) {
this.altText = altText;
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof LogoObject that)) return false;
return Objects.equals(uri, that.uri) && Objects.equals(altText, that.altText);
}
@Override
public int hashCode() {
return Objects.hash(uri, altText);
}
}
/**
* Represents a background image object as defined in the OID4VCI specification.
* {@see https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-issuer-metadata-p}
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class BackgroundImageObject {
@JsonProperty("uri")
private String uri;
public String getUri() {
return uri;
}
public BackgroundImageObject setUri(String uri) {
this.uri = uri;
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof BackgroundImageObject that)) return false;
return Objects.equals(uri, that.uri);
}
@Override
public int hashCode() {
return Objects.hash(uri);
}
}
}

View File

@ -46,17 +46,15 @@ public class SupportedCredentialConfiguration {
@JsonIgnore
private static final String CREDENTIAL_SIGNING_ALG_VALUES_SUPPORTED_KEY = "credential_signing_alg_values_supported";
@JsonIgnore
private static final String DISPLAY_KEY = "display";
@JsonIgnore
private static final String PROOF_TYPES_SUPPORTED_KEY = "proof_types_supported";
@JsonIgnore
private static final String CLAIMS_KEY = "claims";
@JsonIgnore
public static final String VERIFIABLE_CREDENTIAL_TYPE_KEY = "vct";
@JsonIgnore
private static final String CREDENTIAL_DEFINITION_KEY = "credential_definition";
@JsonIgnore
public static final String CREDENTIAL_BUILD_CONFIG_KEY = "credential_build_config";
@JsonIgnore
private static final String CREDENTIAL_METADATA_KEY = "credential_metadata";
private String id;
@ -72,9 +70,6 @@ public class SupportedCredentialConfiguration {
@JsonProperty(CREDENTIAL_SIGNING_ALG_VALUES_SUPPORTED_KEY)
private List<String> credentialSigningAlgValuesSupported;
@JsonProperty(DISPLAY_KEY)
private List<DisplayObject> display;
@JsonProperty(VERIFIABLE_CREDENTIAL_TYPE_KEY)
private String vct;
@ -84,8 +79,8 @@ public class SupportedCredentialConfiguration {
@JsonProperty(PROOF_TYPES_SUPPORTED_KEY)
private ProofTypesSupported proofTypesSupported;
@JsonProperty(CLAIMS_KEY)
private Claims claims;
@JsonProperty(CREDENTIAL_METADATA_KEY)
private CredentialMetadata credentialMetadata;
// This is not a normative field for supported credential metadata,
// but will allow configuring the issuance of the credential internally.
@ -133,16 +128,15 @@ public class SupportedCredentialConfiguration {
String bindingMethodsSupported = CredentialScopeModel.CRYPTOGRAPHIC_BINDING_METHODS_DEFAULT;
credentialConfiguration.setCryptographicBindingMethodsSupported(List.of(bindingMethodsSupported));
credentialConfiguration.setDisplay(DisplayObject.parse(credentialScope));
// Parse credential metadata (includes display and claims)
CredentialMetadata credentialMetadata = CredentialMetadata.parse(keycloakSession, credentialScope);
credentialConfiguration.setCredentialMetadata(credentialMetadata);
CredentialBuildConfig credentialBuildConfig = CredentialBuildConfig.parse(keycloakSession,
credentialConfiguration,
credentialScope);
credentialConfiguration.setCredentialBuildConfig(credentialBuildConfig);
Claims claims = Claims.parse(keycloakSession, credentialScope);
credentialConfiguration.setClaims(claims);
return credentialConfiguration;
}
@ -196,15 +190,6 @@ public class SupportedCredentialConfiguration {
return this;
}
public List<DisplayObject> getDisplay() {
return display;
}
public SupportedCredentialConfiguration setDisplay(List<DisplayObject> display) {
this.display = display;
return this;
}
public String getId() {
return id;
}
@ -223,15 +208,6 @@ public class SupportedCredentialConfiguration {
return this;
}
public Claims getClaims() {
return claims;
}
public SupportedCredentialConfiguration setClaims(Claims claims) {
this.claims = claims;
return this;
}
public String getVct() {
return vct;
}
@ -259,6 +235,15 @@ public class SupportedCredentialConfiguration {
return this;
}
public CredentialMetadata getCredentialMetadata() {
return credentialMetadata;
}
public SupportedCredentialConfiguration setCredentialMetadata(CredentialMetadata credentialMetadata) {
this.credentialMetadata = credentialMetadata;
return this;
}
public CredentialBuildConfig getCredentialBuildConfig() {
return credentialBuildConfig;
}
@ -273,11 +258,11 @@ public class SupportedCredentialConfiguration {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SupportedCredentialConfiguration that = (SupportedCredentialConfiguration) o;
return Objects.equals(id, that.id) && Objects.equals(format, that.format) && Objects.equals(scope, that.scope) && Objects.equals(cryptographicBindingMethodsSupported, that.cryptographicBindingMethodsSupported) && Objects.equals(credentialSigningAlgValuesSupported, that.credentialSigningAlgValuesSupported) && Objects.equals(display, that.display) && Objects.equals(vct, that.vct) && Objects.equals(credentialDefinition, that.credentialDefinition) && Objects.equals(proofTypesSupported, that.proofTypesSupported) && Objects.equals(claims, that.claims) && Objects.equals(credentialBuildConfig, that.credentialBuildConfig);
return Objects.equals(id, that.id) && Objects.equals(format, that.format) && Objects.equals(scope, that.scope) && Objects.equals(cryptographicBindingMethodsSupported, that.cryptographicBindingMethodsSupported) && Objects.equals(credentialSigningAlgValuesSupported, that.credentialSigningAlgValuesSupported) && Objects.equals(vct, that.vct) && Objects.equals(credentialDefinition, that.credentialDefinition) && Objects.equals(proofTypesSupported, that.proofTypesSupported) && Objects.equals(credentialMetadata, that.credentialMetadata) && Objects.equals(credentialBuildConfig, that.credentialBuildConfig);
}
@Override
public int hashCode() {
return Objects.hash(id, format, scope, cryptographicBindingMethodsSupported, credentialSigningAlgValuesSupported, display, vct, credentialDefinition, proofTypesSupported, claims, credentialBuildConfig);
return Objects.hash(id, format, scope, cryptographicBindingMethodsSupported, credentialSigningAlgValuesSupported, vct, credentialDefinition, proofTypesSupported, credentialMetadata, credentialBuildConfig);
}
}

View File

@ -45,6 +45,7 @@ import org.keycloak.protocol.oid4vc.model.ClaimDisplay;
import org.keycloak.protocol.oid4vc.model.Claims;
import org.keycloak.protocol.oid4vc.model.CredentialIssuer;
import org.keycloak.protocol.oid4vc.model.CredentialResponseEncryptionMetadata;
import org.keycloak.protocol.oid4vc.model.CredentialRequestEncryptionMetadata;
import org.keycloak.protocol.oid4vc.model.DisplayObject;
import org.keycloak.protocol.oid4vc.model.Format;
import org.keycloak.protocol.oid4vc.model.ProofTypesSupported;
@ -126,15 +127,21 @@ public class OID4VCIssuerWellKnownProviderTest extends OID4VCIssuerEndpointTest
Assert.assertNotNull("credential_response_encryption should be present", encryption);
Assert.assertEquals(List.of(RSA_OAEP, RSA_OAEP_256), encryption.getAlgValuesSupported());
Assert.assertEquals(List.of(A256GCM), encryption.getEncValuesSupported());
Assert.assertNotNull("zip_values_supported should be present", encryption.getZipValuesSupported());
Assert.assertTrue("encryption_required should be true", encryption.getEncryptionRequired());
// Check credential_request_encryption
CredentialRequestEncryptionMetadata requestEncryption = credentialIssuer.getCredentialRequestEncryption();
Assert.assertNotNull("credential_request_encryption should be present", requestEncryption);
Assert.assertEquals(List.of(A256GCM), requestEncryption.getEncValuesSupported());
Assert.assertNotNull("zip_values_supported should be present", requestEncryption.getZipValuesSupported());
Assert.assertTrue("encryption_required should be true", requestEncryption.getEncryptionRequired());
Assert.assertNotNull("JWKS should be present", requestEncryption.getJwks());
Assert.assertFalse("JWKS should not be empty when encryption keys are available", requestEncryption.getJwks().isEmpty());
CredentialIssuer.BatchCredentialIssuance batch = credentialIssuer.getBatchCredentialIssuance();
Assert.assertNotNull("batch_credential_issuance should be present", batch);
Assert.assertEquals(Integer.valueOf(10), batch.getBatchSize());
Assert.assertEquals(
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.XYZ123abc",
credentialIssuer.getSignedMetadata()
);
for (ClientScopeRepresentation clientScope : List.of(jwtTypeCredentialClientScope,
sdJwtTypeCredentialClientScope,
@ -159,10 +166,10 @@ public class OID4VCIssuerWellKnownProviderTest extends OID4VCIssuerEndpointTest
Assert.assertEquals(clientScope.getName(), supportedConfig.getCredentialDefinition().getType().get(0));
Assert.assertEquals(1, supportedConfig.getCredentialDefinition().getContext().size());
Assert.assertEquals(clientScope.getName(), supportedConfig.getCredentialDefinition().getContext().get(0));
Assert.assertNull(supportedConfig.getDisplay());
Assert.assertNotNull(supportedConfig.getCredentialMetadata());
Assert.assertEquals(clientScope.getName(), supportedConfig.getScope());
compareClaims(supportedConfig.getFormat(), supportedConfig.getClaims(), clientScope.getProtocolMappers());
compareClaims(supportedConfig.getFormat(), supportedConfig.getCredentialMetadata().getClaims(), clientScope.getProtocolMappers());
}
@ -180,10 +187,16 @@ public class OID4VCIssuerWellKnownProviderTest extends OID4VCIssuerEndpointTest
Assert.assertTrue(encryption.getAlgValuesSupported().contains(RSA_OAEP));
Assert.assertTrue("Supported encryption methods should include A256GCM", encryption.getEncValuesSupported().contains(A256GCM));
Assert.assertNotNull("zip_values_supported should be present", encryption.getZipValuesSupported());
Assert.assertTrue(encryption.getEncryptionRequired());
// Check credential_request_encryption
CredentialRequestEncryptionMetadata requestEncryption = issuer.getCredentialRequestEncryption();
Assert.assertNotNull("credential_request_encryption should be present", requestEncryption);
Assert.assertTrue("Supported encryption methods should include A256GCM", requestEncryption.getEncValuesSupported().contains(A256GCM));
Assert.assertNotNull("zip_values_supported should be present", requestEncryption.getZipValuesSupported());
Assert.assertTrue("encryption_required should be true", requestEncryption.getEncryptionRequired());
Assert.assertEquals(Integer.valueOf(10), issuer.getBatchCredentialIssuance().getBatchSize());
Assert.assertEquals("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.XYZ123abc",
issuer.getSignedMetadata());
});
}
@ -216,10 +229,24 @@ public class OID4VCIssuerWellKnownProviderTest extends OID4VCIssuerEndpointTest
oid4vciIssuerConfig.getCredentialResponseEncryption().getAlgValuesSupported().isEmpty());
Assert.assertFalse("Supported encryption methods should not be empty",
oid4vciIssuerConfig.getCredentialResponseEncryption().getEncValuesSupported().isEmpty());
Assert.assertNotNull("zip_values_supported should be present",
oid4vciIssuerConfig.getCredentialResponseEncryption().getZipValuesSupported());
Assert.assertTrue("Supported algorithms should include RSA-OAEP",
oid4vciIssuerConfig.getCredentialResponseEncryption().getAlgValuesSupported().contains("RSA-OAEP"));
Assert.assertTrue("Supported encryption methods should include A256GCM",
oid4vciIssuerConfig.getCredentialResponseEncryption().getEncValuesSupported().contains("A256GCM"));
Assert.assertNotNull("Credential request encryption should be advertised in metadata",
oid4vciIssuerConfig.getCredentialRequestEncryption());
Assert.assertFalse("Supported encryption methods should not be empty",
oid4vciIssuerConfig.getCredentialRequestEncryption().getEncValuesSupported().isEmpty());
Assert.assertNotNull("zip_values_supported should be present",
oid4vciIssuerConfig.getCredentialRequestEncryption().getZipValuesSupported());
Assert.assertTrue("Supported encryption methods should include A256GCM",
oid4vciIssuerConfig.getCredentialRequestEncryption().getEncValuesSupported().contains("A256GCM"));
Assert.assertNotNull("JWKS should be present in credential request encryption",
oid4vciIssuerConfig.getCredentialRequestEncryption().getJwks());
Assert.assertFalse("JWKS should not be empty when encryption keys are available",
oid4vciIssuerConfig.getCredentialRequestEncryption().getJwks().isEmpty());
}
}
}
@ -292,13 +319,14 @@ public class OID4VCIssuerWellKnownProviderTest extends OID4VCIssuerEndpointTest
throw new RuntimeException(e);
}
compareClaims(expectedFormat, supportedConfig.getClaims(), clientScope.getProtocolMappers());
compareClaims(expectedFormat, supportedConfig.getCredentialMetadata().getClaims(), clientScope.getProtocolMappers());
}
private void compareDisplay(SupportedCredentialConfiguration supportedConfig, ClientScopeRepresentation clientScope) {
String display = clientScope.getAttributes().get(CredentialScopeModel.VC_DISPLAY);
if (StringUtil.isBlank(display)) {
Assert.assertNull(supportedConfig.getDisplay());
Assert.assertNull(supportedConfig.getCredentialMetadata() != null ? supportedConfig.getCredentialMetadata().getDisplay() : null);
return;
}
List<DisplayObject> expectedDisplayObjectList;
@ -309,9 +337,10 @@ public class OID4VCIssuerWellKnownProviderTest extends OID4VCIssuerEndpointTest
throw new RuntimeException(e);
}
Assert.assertEquals(expectedDisplayObjectList.size(), supportedConfig.getDisplay().size());
Assert.assertNotNull("Credential metadata should exist when display is configured", supportedConfig.getCredentialMetadata());
Assert.assertEquals(expectedDisplayObjectList.size(), supportedConfig.getCredentialMetadata().getDisplay().size());
MatcherAssert.assertThat("Must contain all expected display-objects",
supportedConfig.getDisplay(),
supportedConfig.getCredentialMetadata().getDisplay(),
Matchers.containsInAnyOrder(expectedDisplayObjectList.toArray()));
}
@ -410,7 +439,9 @@ public class OID4VCIssuerWellKnownProviderTest extends OID4VCIssuerEndpointTest
Assert.assertTrue("The test-credential should be supported.", credentialIssuer.getCredentialsSupported().containsKey("test-credential"));
Assert.assertEquals("The test-credential should offer type VerifiableCredential", "VerifiableCredential", credentialIssuer.getCredentialsSupported().get("test-credential").getScope());
Assert.assertEquals("The test-credential should be offered in the jwt-vc format.", Format.JWT_VC, credentialIssuer.getCredentialsSupported().get("test-credential").getFormat());
Assert.assertNotNull("The test-credential can optionally provide a claims claim.", credentialIssuer.getCredentialsSupported().get("test-credential").getClaims());
Assert.assertNotNull("The test-credential can optionally provide a claims claim.",
credentialIssuer.getCredentialsSupported().get("test-credential").getCredentialMetadata() != null ?
credentialIssuer.getCredentialsSupported().get("test-credential").getCredentialMetadata().getClaims() : null);
}));
}

View File

@ -936,7 +936,7 @@ public class OID4VCJWTIssuerEndpointTest extends OID4VCIssuerEndpointTest {
Format.JWT_VC,
jwtVcConfig.getFormat());
Claims jwtVcClaims = jwtVcConfig.getClaims();
Claims jwtVcClaims = jwtVcConfig.getCredentialMetadata() != null ? jwtVcConfig.getCredentialMetadata().getClaims() : null;
assertNotNull("The jwt_vc-credential can optionally provide a claims claim.",
jwtVcClaims);
@ -1047,7 +1047,8 @@ public class OID4VCJWTIssuerEndpointTest extends OID4VCIssuerEndpointTest {
.contains("RS256"));
assertEquals("The jwt_vc-credential should display as Test Credential",
credentialConfigurationId,
jwtVcConfig.getDisplay().get(0).getName());
jwtVcConfig.getCredentialMetadata() != null && jwtVcConfig.getCredentialMetadata().getDisplay() != null ?
jwtVcConfig.getCredentialMetadata().getDisplay().get(0).getName() : null);
}));
}

View File

@ -395,9 +395,11 @@ public class OID4VCSdJwtIssuingEndpointTest extends OID4VCIssuerEndpointTest {
assertNotNull("The sd-jwt-credential can optionally provide a claims claim.",
credentialIssuer.getCredentialsSupported().get(credentialConfigurationId)
.getClaims());
.getCredentialMetadata() != null ?
credentialIssuer.getCredentialsSupported().get(credentialConfigurationId)
.getCredentialMetadata().getClaims() : null);
Claims jwtVcClaims = jwtVcConfig.getClaims();
Claims jwtVcClaims = jwtVcConfig.getCredentialMetadata() != null ? jwtVcConfig.getCredentialMetadata().getClaims() : null;
assertNotNull("The sd-jwt-credential can optionally provide a claims claim.",
jwtVcClaims);
@ -476,7 +478,11 @@ public class OID4VCSdJwtIssuingEndpointTest extends OID4VCIssuerEndpointTest {
assertEquals("The sd-jwt-credential should display as Test Credential",
credentialConfigurationId,
credentialIssuer.getCredentialsSupported().get(credentialConfigurationId)
.getDisplay().get(0).getName());
.getCredentialMetadata() != null &&
credentialIssuer.getCredentialsSupported().get(credentialConfigurationId)
.getCredentialMetadata().getDisplay() != null ?
credentialIssuer.getCredentialsSupported().get(credentialConfigurationId)
.getCredentialMetadata().getDisplay().get(0).getName() : null);
}));
}