[java-idp-plugin-vci] 01/01: More extensive parsing of issuer metadata. Validate pre-authorized credentials match issuer metadata for mandatory claims
Codeberg
noreply at shibboleth.net
Tue Jul 28 13:37:53 UTC 2026
This is an automated email from the git hooks/post-receive script.
codeberg pushed a commit to branch dev/CREDCONFIMPRV
in repository java-idp-plugin-vci.
View the commit online:
https://codeberg.org/Shibboleth/java-idp-plugin-vci/commit/929c434cb4b4af20916a253f901a503d0115c30c
commit 929c434cb4b4af20916a253f901a503d0115c30c
Author: Janne Lauros <janne.lauros at csc.fi>
AuthorDate: Tue Jul 28 16:37:29 2026 +0300
More extensive parsing of issuer metadata. Validate pre-authorized credentials match issuer metadata for mandatory claims
---
.../openidvci/credential/ClaimDescription.java | 108 +++++++++++++
.../plugin/openidvci/credential/ClaimDisplay.java | 79 ++++++++++
.../credential/CredentialConfiguration.java | 83 +++++++++-
.../openidvci/credential/CredentialDisplay.java | 173 ++++++++++++++++++++
.../openidvci/credential/CredentialMetadata.java | 81 ++++++++++
.../plugin/openidvci/credential/Image.java | 80 ++++++++++
.../credential/KeyAttestationsRequired.java | 87 +++++++++++
.../openidvci/credential/ProofTypeSupported.java | 91 +++++++++++
.../credential/CredentialConfigurationTest.java | 105 +++++++++++++
.../profile/impl/ValidateCredentialOffering.java | 95 ++++++++++-
.../impl/ValidateCredentialOfferingTest.java | 69 +++++++-
.../resources/conf/verifiable-credentials.json | 174 +++++++++++----------
12 files changed, 1127 insertions(+), 98 deletions(-)
diff --git a/openid-vci-api/src/main/java/org/geant/shibboleth/plugin/openidvci/credential/ClaimDescription.java b/openid-vci-api/src/main/java/org/geant/shibboleth/plugin/openidvci/credential/ClaimDescription.java
new file mode 100644
index 0000000..fc0642d
--- /dev/null
+++ b/openid-vci-api/src/main/java/org/geant/shibboleth/plugin/openidvci/credential/ClaimDescription.java
@@ -0,0 +1,108 @@
+/*
+ * 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.credential;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Description of a single claim inside {@link CredentialMetadata#getClaims()}
+ * as defined in
+ * {@link https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#claims-description-issuer-metadata}.
+ */
+ at JsonIgnoreProperties(ignoreUnknown = true)
+public class ClaimDescription {
+
+ /**
+ * JSON path locating the claim within the issued Credential. Each element is
+ * either a string (property name), a non-negative integer (array index), or
+ * null (all elements). Modeled as {@link Object} to accept all three.
+ */
+ @Nonnull
+ @JsonProperty("path")
+ private final List<Object> path;
+
+ /** Display properties for this claim per language. */
+ @Nullable
+ @JsonProperty("display")
+ @JsonInclude(Include.NON_ABSENT)
+ private final List<ClaimDisplay> display;
+
+ /** Whether Credential Issuer will always include this claim in the issued Credential. */
+ @Nullable
+ @JsonProperty("mandatory")
+ @JsonInclude(Include.NON_NULL)
+ private final Boolean mandatory;
+
+ /**
+ * Constructor.
+ *
+ * @param path JSON path locating the claim within the issued Credential
+ * @param display Display properties for this claim per language
+ * @param mandatory Whether Credential Issuer will always include this claim in
+ * the issued Credential
+ */
+ @JsonCreator
+ private ClaimDescription(@JsonProperty("path") @Nonnull List<Object> path,
+ @JsonProperty("display") @Nullable List<ClaimDisplay> display,
+ @JsonProperty("mandatory") @Nullable Boolean mandatory) {
+ assert path != null;
+ this.path = path;
+ this.display = display;
+ this.mandatory = mandatory;
+ }
+
+ /**
+ * Get JSON path locating the claim within the issued Credential.
+ *
+ * @return JSON path locating the claim within the issued Credential
+ */
+ @Nonnull
+ public List<Object> getPath() {
+ return path;
+ }
+
+ /**
+ * Get display properties for this claim per language.
+ *
+ * @return Display properties for this claim per language
+ */
+ @Nullable
+ public List<ClaimDisplay> getDisplay() {
+ return display;
+ }
+
+ /**
+ * Get whether Credential Issuer will always include this claim in the issued
+ * Credential.
+ *
+ * @return Whether Credential Issuer will always include this claim in the
+ * issued Credential
+ */
+ @Nullable
+ public Boolean getMandatory() {
+ return mandatory;
+ }
+}
diff --git a/openid-vci-api/src/main/java/org/geant/shibboleth/plugin/openidvci/credential/ClaimDisplay.java b/openid-vci-api/src/main/java/org/geant/shibboleth/plugin/openidvci/credential/ClaimDisplay.java
new file mode 100644
index 0000000..edf5459
--- /dev/null
+++ b/openid-vci-api/src/main/java/org/geant/shibboleth/plugin/openidvci/credential/ClaimDisplay.java
@@ -0,0 +1,79 @@
+/*
+ * 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.credential;
+
+import javax.annotation.Nullable;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Display properties of a single claim for a particular language, used inside
+ * {@link ClaimDescription#getDisplay()} as defined in
+ * {@link https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#claims-description-issuer-metadata}.
+ */
+ at JsonIgnoreProperties(ignoreUnknown = true)
+public class ClaimDisplay {
+
+ /** Display name of the claim. */
+ @Nullable
+ @JsonProperty("name")
+ @JsonInclude(Include.NON_NULL)
+ private final String name;
+
+ /** Language tag (BCP47) identifying the language of this object. */
+ @Nullable
+ @JsonProperty("locale")
+ @JsonInclude(Include.NON_NULL)
+ private final String locale;
+
+ /**
+ * Constructor.
+ *
+ * @param name Display name of the claim
+ * @param locale Language tag identifying the language of this object
+ */
+ @JsonCreator
+ private ClaimDisplay(@JsonProperty("name") @Nullable String name,
+ @JsonProperty("locale") @Nullable String locale) {
+ this.name = name;
+ this.locale = locale;
+ }
+
+ /**
+ * Get display name of the claim.
+ *
+ * @return Display name of the claim
+ */
+ @Nullable
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Get language tag identifying the language of this object.
+ *
+ * @return Language tag identifying the language of this object
+ */
+ @Nullable
+ public String getLocale() {
+ return locale;
+ }
+}
diff --git a/openid-vci-api/src/main/java/org/geant/shibboleth/plugin/openidvci/credential/CredentialConfiguration.java b/openid-vci-api/src/main/java/org/geant/shibboleth/plugin/openidvci/credential/CredentialConfiguration.java
index 4ddad53..467f5db 100644
--- a/openid-vci-api/src/main/java/org/geant/shibboleth/plugin/openidvci/credential/CredentialConfiguration.java
+++ b/openid-vci-api/src/main/java/org/geant/shibboleth/plugin/openidvci/credential/CredentialConfiguration.java
@@ -17,6 +17,7 @@
package org.geant.shibboleth.plugin.openidvci.credential;
import java.util.List;
+import java.util.Map;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -34,8 +35,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
*
* Credential configurations are defined in
* {@link https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-issuer-metadata-p}
- * as parameter credential_configurations_supported. Still only a partial
- * implementation of the parameters.
+ * as parameter credential_configurations_supported.
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class CredentialConfiguration {
@@ -79,9 +79,32 @@ public class CredentialConfiguration {
@JsonInclude(Include.NON_NULL)
private final CredentialDefinition credentialDefinition;
+ /**
+ * Specifics of the key proof(s) that the Credential Issuer supports
+ */
+ @Nullable
+ @JsonProperty("proof_types_supported")
+ @JsonInclude(Include.NON_ABSENT)
+ private final Map<String, ProofTypeSupported> proofTypesSupported;
+
+ /** Information relevant to the usage and display of issued Credentials. */
+ @Nullable
+ @JsonProperty("credential_metadata")
+ @JsonInclude(Include.NON_NULL)
+ private final CredentialMetadata credentialMetadata;
+
+ /**
+ * Verifiable Credential Type identifier. Required for SD-JWT VC formats
+ * ({@code dc+sd-jwt} / {@code vc+sd-jwt}).
+ */
+ @Nullable
+ @JsonProperty("vct")
+ @JsonInclude(Include.NON_NULL)
+ private final String vct;
+
/**
* Constructor.
- *
+ *
* @param format Identifies the format of this
* Credential
* @param scope Identifies the scope value that
@@ -99,24 +122,42 @@ public class CredentialConfiguration {
* @param credentialDefinition Object containing the detailed
* description of the Credential
* type
+ * @param proofTypesSupported Specifics of the key proof(s)
+ * that the Credential Issuer
+ * supports
+ * @param credentialMetadata Information relevant to the
+ * usage and display of issued
+ * Credentials
+ * @param vct Verifiable Credential Type
+ * identifier (SD-JWT VC formats)
*/
@JsonCreator
private CredentialConfiguration(@JsonProperty("format") @Nonnull String format,
@JsonProperty("scope") @Nullable String scope,
@JsonProperty("credential_signing_alg_values_supported") @Nullable List<String> credentialSigningAlgValuesSupported,
@JsonProperty("cryptographic_binding_methods_supported") @Nullable List<String> cryptographicBindingMethodsSupported,
- @JsonProperty("credential_definition") @Nullable CredentialDefinition credentialDefinition) {
+ @JsonProperty("credential_definition") @Nullable CredentialDefinition credentialDefinition,
+ @JsonProperty("proof_types_supported") @Nullable Map<String, ProofTypeSupported> proofTypesSupported,
+ @JsonProperty("credential_metadata") @Nullable CredentialMetadata credentialMetadata,
+ @JsonProperty("vct") @Nullable String vct) {
assert format != null;
this.format = format;
this.scope = scope;
this.credentialSigningAlgValuesSupported = credentialSigningAlgValuesSupported;
this.cryptographicBindingMethodsSupported = cryptographicBindingMethodsSupported;
this.credentialDefinition = credentialDefinition;
+ this.proofTypesSupported = proofTypesSupported;
+ this.credentialMetadata = credentialMetadata;
+ this.vct = vct;
// W3C family of credentials require credential definition
if (credentialDefinition == null
- && ("jwt_vc_json-ld".equals(format) || "jwt_vc_json".equals(format) || "ldp_vc".equals(format))) {
+ && "vc+sd-jwt".equals(format)) {
throw new IllegalArgumentException("credential_definition is missing");
}
+ // SD-JWT VC family requires vct
+ if (vct == null && "dc+sd-jwt".equals(format)) {
+ throw new IllegalArgumentException("vct is missing");
+ }
}
/**
@@ -168,7 +209,7 @@ public class CredentialConfiguration {
/**
* Get object containing the detailed description of the Credential type.
- *
+ *
* @return Object containing the detailed description of the Credential type
*/
@Nullable
@@ -176,6 +217,36 @@ public class CredentialConfiguration {
return credentialDefinition;
}
+ /**
+ * Get specifics of the key proof(s) that the Credential Issuer supports.
+ *
+ * @return Specifics of the key proof(s) that the Credential Issuer supports
+ */
+ @Nullable
+ public Map<String, ProofTypeSupported> getProofTypesSupported() {
+ return proofTypesSupported;
+ }
+
+ /**
+ * Get information relevant to the usage and display of issued Credentials.
+ *
+ * @return Information relevant to the usage and display of issued Credentials
+ */
+ @Nullable
+ public CredentialMetadata getCredentialMetadata() {
+ return credentialMetadata;
+ }
+
+ /**
+ * Get Verifiable Credential Type identifier (SD-JWT VC formats).
+ *
+ * @return Verifiable Credential Type identifier
+ */
+ @Nullable
+ public String getVct() {
+ return vct;
+ }
+
/**
* Serialize object to json.
*
diff --git a/openid-vci-api/src/main/java/org/geant/shibboleth/plugin/openidvci/credential/CredentialDisplay.java b/openid-vci-api/src/main/java/org/geant/shibboleth/plugin/openidvci/credential/CredentialDisplay.java
new file mode 100644
index 0000000..34dbdf5
--- /dev/null
+++ b/openid-vci-api/src/main/java/org/geant/shibboleth/plugin/openidvci/credential/CredentialDisplay.java
@@ -0,0 +1,173 @@
+/*
+ * 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.credential;
+
+import javax.annotation.Nullable;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Display properties of a supported Credential for a particular language, used
+ * inside {@link CredentialMetadata#getDisplay()} as defined in
+ * {@link https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-issuer-metadata-p}.
+ */
+ at JsonIgnoreProperties(ignoreUnknown = true)
+public class CredentialDisplay {
+
+ /** Display name of the Credential. */
+ @Nullable
+ @JsonProperty("name")
+ @JsonInclude(Include.NON_NULL)
+ private final String name;
+
+ /** Language tag (BCP47) identifying the language of this object. */
+ @Nullable
+ @JsonProperty("locale")
+ @JsonInclude(Include.NON_NULL)
+ private final String locale;
+
+ /** Logo of the Credential. */
+ @Nullable
+ @JsonProperty("logo")
+ @JsonInclude(Include.NON_NULL)
+ private final Image logo;
+
+ /** Description of the Credential. */
+ @Nullable
+ @JsonProperty("description")
+ @JsonInclude(Include.NON_NULL)
+ private final String description;
+
+ /** Background color of the Credential (CSS color value). */
+ @Nullable
+ @JsonProperty("background_color")
+ @JsonInclude(Include.NON_NULL)
+ private final String backgroundColor;
+
+ /** Background image of the Credential. */
+ @Nullable
+ @JsonProperty("background_image")
+ @JsonInclude(Include.NON_NULL)
+ private final Image backgroundImage;
+
+ /** Text color of the Credential (CSS color value). */
+ @Nullable
+ @JsonProperty("text_color")
+ @JsonInclude(Include.NON_NULL)
+ private final String textColor;
+
+ /**
+ * Constructor.
+ *
+ * @param name Display name of the Credential
+ * @param locale Language tag identifying the language of this object
+ * @param logo Logo of the Credential
+ * @param description Description of the Credential
+ * @param backgroundColor Background color of the Credential
+ * @param backgroundImage Background image of the Credential
+ * @param textColor Text color of the Credential
+ */
+ @JsonCreator
+ private CredentialDisplay(@JsonProperty("name") @Nullable String name,
+ @JsonProperty("locale") @Nullable String locale, @JsonProperty("logo") @Nullable Image logo,
+ @JsonProperty("description") @Nullable String description,
+ @JsonProperty("background_color") @Nullable String backgroundColor,
+ @JsonProperty("background_image") @Nullable Image backgroundImage,
+ @JsonProperty("text_color") @Nullable String textColor) {
+ this.name = name;
+ this.locale = locale;
+ this.logo = logo;
+ this.description = description;
+ this.backgroundColor = backgroundColor;
+ this.backgroundImage = backgroundImage;
+ this.textColor = textColor;
+ }
+
+ /**
+ * Get display name of the Credential.
+ *
+ * @return Display name of the Credential
+ */
+ @Nullable
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Get language tag identifying the language of this object.
+ *
+ * @return Language tag identifying the language of this object
+ */
+ @Nullable
+ public String getLocale() {
+ return locale;
+ }
+
+ /**
+ * Get logo of the Credential.
+ *
+ * @return Logo of the Credential
+ */
+ @Nullable
+ public Image getLogo() {
+ return logo;
+ }
+
+ /**
+ * Get description of the Credential.
+ *
+ * @return Description of the Credential
+ */
+ @Nullable
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * Get background color of the Credential.
+ *
+ * @return Background color of the Credential
+ */
+ @Nullable
+ public String getBackgroundColor() {
+ return backgroundColor;
+ }
+
+ /**
+ * Get background image of the Credential.
+ *
+ * @return Background image of the Credential
+ */
+ @Nullable
+ public Image getBackgroundImage() {
+ return backgroundImage;
+ }
+
+ /**
+ * Get text color of the Credential.
+ *
+ * @return Text color of the Credential
+ */
+ @Nullable
+ public String getTextColor() {
+ return textColor;
+ }
+}
diff --git a/openid-vci-api/src/main/java/org/geant/shibboleth/plugin/openidvci/credential/CredentialMetadata.java b/openid-vci-api/src/main/java/org/geant/shibboleth/plugin/openidvci/credential/CredentialMetadata.java
new file mode 100644
index 0000000..b42cbc1
--- /dev/null
+++ b/openid-vci-api/src/main/java/org/geant/shibboleth/plugin/openidvci/credential/CredentialMetadata.java
@@ -0,0 +1,81 @@
+/*
+ * 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.credential;
+
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Credential metadata parameter of a {@link CredentialConfiguration} as defined
+ * in
+ * {@link https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-issuer-metadata-p}.
+ */
+ at JsonIgnoreProperties(ignoreUnknown = true)
+public class CredentialMetadata {
+
+ /** Display properties per language. */
+ @Nullable
+ @JsonProperty("display")
+ @JsonInclude(Include.NON_ABSENT)
+ private final List<CredentialDisplay> display;
+
+ /** Description of the claims carried by this Credential. */
+ @Nullable
+ @JsonProperty("claims")
+ @JsonInclude(Include.NON_ABSENT)
+ private final List<ClaimDescription> claims;
+
+ /**
+ * Constructor.
+ *
+ * @param display Display properties per language
+ * @param claims Description of the claims carried by this Credential
+ */
+ @JsonCreator
+ private CredentialMetadata(@JsonProperty("display") @Nullable List<CredentialDisplay> display,
+ @JsonProperty("claims") @Nullable List<ClaimDescription> claims) {
+ this.display = display;
+ this.claims = claims;
+ }
+
+ /**
+ * Get display properties per language.
+ *
+ * @return Display properties per language
+ */
+ @Nullable
+ public List<CredentialDisplay> getDisplay() {
+ return display;
+ }
+
+ /**
+ * Get description of the claims carried by this Credential.
+ *
+ * @return Description of the claims carried by this Credential
+ */
+ @Nullable
+ public List<ClaimDescription> getClaims() {
+ return claims;
+ }
+}
diff --git a/openid-vci-api/src/main/java/org/geant/shibboleth/plugin/openidvci/credential/Image.java b/openid-vci-api/src/main/java/org/geant/shibboleth/plugin/openidvci/credential/Image.java
new file mode 100644
index 0000000..eacb671
--- /dev/null
+++ b/openid-vci-api/src/main/java/org/geant/shibboleth/plugin/openidvci/credential/Image.java
@@ -0,0 +1,80 @@
+/*
+ * 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.credential;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Image reference used by {@link CredentialDisplay#getLogo()} and
+ * {@link CredentialDisplay#getBackgroundImage()}. The spec defines
+ * {@code alt_text} only for {@code logo}; leaving it {@code null} yields a
+ * valid {@code background_image} object.
+ */
+ at JsonIgnoreProperties(ignoreUnknown = true)
+public class Image {
+
+ /** URI where the Wallet can obtain the image from the Credential Issuer. */
+ @Nonnull
+ @JsonProperty("uri")
+ private final String uri;
+
+ /** Alternative text for the image (logo only per the spec). */
+ @Nullable
+ @JsonProperty("alt_text")
+ @JsonInclude(Include.NON_NULL)
+ private final String altText;
+
+ /**
+ * Constructor.
+ *
+ * @param uri URI where the Wallet can obtain the image
+ * @param altText Alternative text for the image
+ */
+ @JsonCreator
+ private Image(@JsonProperty("uri") @Nonnull String uri, @JsonProperty("alt_text") @Nullable String altText) {
+ assert uri != null;
+ this.uri = uri;
+ this.altText = altText;
+ }
+
+ /**
+ * Get URI where the Wallet can obtain the image.
+ *
+ * @return URI where the Wallet can obtain the image
+ */
+ @Nonnull
+ public String getUri() {
+ return uri;
+ }
+
+ /**
+ * Get alternative text for the image.
+ *
+ * @return Alternative text for the image
+ */
+ @Nullable
+ public String getAltText() {
+ return altText;
+ }
+}
diff --git a/openid-vci-api/src/main/java/org/geant/shibboleth/plugin/openidvci/credential/KeyAttestationsRequired.java b/openid-vci-api/src/main/java/org/geant/shibboleth/plugin/openidvci/credential/KeyAttestationsRequired.java
new file mode 100644
index 0000000..694c974
--- /dev/null
+++ b/openid-vci-api/src/main/java/org/geant/shibboleth/plugin/openidvci/credential/KeyAttestationsRequired.java
@@ -0,0 +1,87 @@
+/*
+ * 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.credential;
+
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Key attestations required inside {@link ProofTypeSupported} as defined in
+ * {@link https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-key-attestations-in-jwt-pro}.
+ */
+ at JsonIgnoreProperties(ignoreUnknown = true)
+public class KeyAttestationsRequired {
+
+ /**
+ * Resistance level required for the key storage component of the attested key.
+ */
+ @Nullable
+ @JsonProperty("key_storage")
+ @JsonInclude(Include.NON_ABSENT)
+ private final List<String> keyStorage;
+
+ /**
+ * Resistance level required for the user authentication component of the
+ * attested key.
+ */
+ @Nullable
+ @JsonProperty("user_authentication")
+ @JsonInclude(Include.NON_ABSENT)
+ private final List<String> userAuthentication;
+
+ /**
+ * Constructor.
+ *
+ * @param keyStorage Resistance levels the key storage component must
+ * satisfy
+ * @param userAuthentication Resistance levels the user authentication component
+ * must satisfy
+ */
+ @JsonCreator
+ private KeyAttestationsRequired(@JsonProperty("key_storage") @Nullable List<String> keyStorage,
+ @JsonProperty("user_authentication") @Nullable List<String> userAuthentication) {
+ this.keyStorage = keyStorage;
+ this.userAuthentication = userAuthentication;
+ }
+
+ /**
+ * Get resistance levels the key storage component must satisfy.
+ *
+ * @return Resistance levels the key storage component must satisfy
+ */
+ @Nullable
+ public List<String> getKeyStorage() {
+ return keyStorage;
+ }
+
+ /**
+ * Get resistance levels the user authentication component must satisfy.
+ *
+ * @return Resistance levels the user authentication component must satisfy
+ */
+ @Nullable
+ public List<String> getUserAuthentication() {
+ return userAuthentication;
+ }
+}
diff --git a/openid-vci-api/src/main/java/org/geant/shibboleth/plugin/openidvci/credential/ProofTypeSupported.java b/openid-vci-api/src/main/java/org/geant/shibboleth/plugin/openidvci/credential/ProofTypeSupported.java
new file mode 100644
index 0000000..ddef74a
--- /dev/null
+++ b/openid-vci-api/src/main/java/org/geant/shibboleth/plugin/openidvci/credential/ProofTypeSupported.java
@@ -0,0 +1,91 @@
+/*
+ * 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.credential;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Metadata about a single key proof type advertised in
+ * {@link CredentialConfiguration#getProofTypesSupported()} as defined in
+ * {@link https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-issuer-metadata-p}.
+ */
+ at JsonIgnoreProperties(ignoreUnknown = true)
+public class ProofTypeSupported {
+
+ /**
+ * Algorithm identifiers the Issuer supports for this proof type.
+ */
+ @Nonnull
+ @JsonProperty("proof_signing_alg_values_supported")
+ private final List<String> proofSigningAlgValuesSupported;
+
+ /**
+ * Key attestation requirements the Wallet must satisfy for this proof type.
+ */
+ @Nullable
+ @JsonProperty("key_attestations_required")
+ @JsonInclude(Include.NON_NULL)
+ private final KeyAttestationsRequired keyAttestationsRequired;
+
+ /**
+ * Constructor.
+ *
+ * @param proofSigningAlgValuesSupported Algorithm identifiers the Issuer
+ * supports for this proof type
+ * @param keyAttestationsRequired Key attestation requirements the Wallet
+ * must satisfy for this proof type
+ */
+ @JsonCreator
+ private ProofTypeSupported(
+ @JsonProperty("proof_signing_alg_values_supported") @Nonnull List<String> proofSigningAlgValuesSupported,
+ @JsonProperty("key_attestations_required") @Nullable KeyAttestationsRequired keyAttestationsRequired) {
+ assert proofSigningAlgValuesSupported != null;
+ this.proofSigningAlgValuesSupported = proofSigningAlgValuesSupported;
+ this.keyAttestationsRequired = keyAttestationsRequired;
+ }
+
+ /**
+ * Get algorithm identifiers the Issuer supports for this proof type.
+ *
+ * @return Algorithm identifiers the Issuer supports for this proof type
+ */
+ @Nonnull
+ public List<String> getProofSigningAlgValuesSupported() {
+ return proofSigningAlgValuesSupported;
+ }
+
+ /**
+ * Get key attestation requirements the Wallet must satisfy for this proof
+ * type.
+ *
+ * @return Key attestation requirements the Wallet must satisfy for this proof
+ * type
+ */
+ @Nullable
+ public KeyAttestationsRequired getKeyAttestationsRequired() {
+ return keyAttestationsRequired;
+ }
+}
diff --git a/openid-vci-api/src/test/java/org/geant/shibboleth/plugin/openidvci/credential/CredentialConfigurationTest.java b/openid-vci-api/src/test/java/org/geant/shibboleth/plugin/openidvci/credential/CredentialConfigurationTest.java
index 211f5e8..99b33bf 100644
--- a/openid-vci-api/src/test/java/org/geant/shibboleth/plugin/openidvci/credential/CredentialConfigurationTest.java
+++ b/openid-vci-api/src/test/java/org/geant/shibboleth/plugin/openidvci/credential/CredentialConfigurationTest.java
@@ -17,6 +17,7 @@
package org.geant.shibboleth.plugin.openidvci.credential;
import java.io.IOException;
+import java.util.List;
import org.springframework.core.io.ClassPathResource;
import org.testng.Assert;
@@ -52,6 +53,29 @@ public class CredentialConfigurationTest {
Assert.assertEquals(configuration.getCredentialSigningAlgValuesSupported().get(0), "ES256");
Assert.assertEquals(configuration.getCryptographicBindingMethodsSupported().get(0), "jwk");
+ ProofTypeSupported jwtProof = configuration.getProofTypesSupported().get("jwt");
+ Assert.assertNotNull(jwtProof);
+ Assert.assertEquals(jwtProof.getProofSigningAlgValuesSupported().get(0), "ES256");
+ Assert.assertEquals(jwtProof.getKeyAttestationsRequired().getKeyStorage().get(0), "iso_18045_moderate");
+ Assert.assertEquals(jwtProof.getKeyAttestationsRequired().getUserAuthentication().get(0),
+ "iso_18045_moderate");
+
+ Assert.assertEquals(configuration.getVct(), "SD_JWT_VC_example_in_OpenID4VCI");
+
+ CredentialDisplay display = configuration.getCredentialMetadata().getDisplay().get(0);
+ Assert.assertEquals(display.getName(), "IdentityCredential");
+ Assert.assertEquals(display.getLocale(), "en-US");
+ Assert.assertEquals(display.getLogo().getUri(), "https://university.example.edu/public/logo.png");
+ Assert.assertEquals(display.getLogo().getAltText(), "a square logo of a university");
+ Assert.assertEquals(display.getBackgroundColor(), "#12107c");
+ Assert.assertEquals(display.getTextColor(), "#FFFFFF");
+
+ List<ClaimDescription> claims = configuration.getCredentialMetadata().getClaims();
+ Assert.assertEquals(claims.get(0).getPath().get(0), "given_name");
+ Assert.assertEquals(claims.get(0).getDisplay().get(0).getName(), "Given Name");
+ Assert.assertEquals(claims.get(0).getDisplay().get(0).getLocale(), "en-US");
+ Assert.assertEquals(claims.get(0).getDisplay().get(1).getName(), "Vorname");
+ Assert.assertNull(claims.get(0).getMandatory());
}
@Test
@@ -65,7 +89,88 @@ public class CredentialConfigurationTest {
Assert.assertNull(configuration.getScope());
Assert.assertEquals(configuration.getCredentialSigningAlgValuesSupported().get(0), "Ed25519Signature2018");
Assert.assertEquals(configuration.getCryptographicBindingMethodsSupported().get(0), "did:example");
+ Assert.assertNull(configuration.getProofTypesSupported());
+ Assert.assertNull(configuration.getVct());
+
+ CredentialDisplay display = configuration.getCredentialMetadata().getDisplay().get(0);
+ Assert.assertEquals(display.getName(), "University Credential");
+ Assert.assertEquals(display.getLocale(), "en-US");
+ Assert.assertEquals(display.getLogo().getUri(), "https://university.example.edu/public/logo.png");
+
+ List<ClaimDescription> claims = configuration.getCredentialMetadata().getClaims();
+ Assert.assertEquals(claims.get(3).getPath().get(0), "credentialSubject");
+ Assert.assertEquals(claims.get(3).getPath().get(1), "gpa");
+ Assert.assertEquals(claims.get(3).getMandatory(), Boolean.TRUE);
+ Assert.assertEquals(claims.get(3).getDisplay().get(0).getName(), "GPA");
+ }
+
+ @Test
+ public void parseMinimalTopLevel() throws JsonProcessingException {
+ CredentialConfiguration configuration = CredentialConfiguration
+ .parse("{\"format\":\"dc+sd-jwt\",\"vct\":\"MinimalType\"}");
+ Assert.assertEquals(configuration.getFormat(), "dc+sd-jwt");
+ Assert.assertEquals(configuration.getVct(), "MinimalType");
+ Assert.assertNull(configuration.getScope());
+ Assert.assertNull(configuration.getCredentialSigningAlgValuesSupported());
+ Assert.assertNull(configuration.getCryptographicBindingMethodsSupported());
+ Assert.assertNull(configuration.getCredentialDefinition());
+ Assert.assertNull(configuration.getProofTypesSupported());
+ Assert.assertNull(configuration.getCredentialMetadata());
+ }
+
+ @Test
+ public void parseMinimalNested() throws JsonProcessingException {
+ String json = "{\"format\":\"dc+sd-jwt\",\"vct\":\"MinimalType\","
+ + "\"proof_types_supported\":{\"jwt\":{\"proof_signing_alg_values_supported\":[\"ES256\"]}},"
+ + "\"credential_metadata\":{\"display\":[{\"name\":\"Minimal\","
+ + "\"background_image\":{\"uri\":\"https://example.org/bg.png\"}}],"
+ + "\"claims\":[{\"path\":[\"given_name\"]}]}}";
+ CredentialConfiguration configuration = CredentialConfiguration.parse(json);
+
+ ProofTypeSupported jwtProof = configuration.getProofTypesSupported().get("jwt");
+ Assert.assertNotNull(jwtProof);
+ Assert.assertEquals(jwtProof.getProofSigningAlgValuesSupported().get(0), "ES256");
+ Assert.assertNull(jwtProof.getKeyAttestationsRequired());
+
+ CredentialDisplay display = configuration.getCredentialMetadata().getDisplay().get(0);
+ Assert.assertEquals(display.getName(), "Minimal");
+ Assert.assertNull(display.getLocale());
+ Assert.assertNull(display.getLogo());
+ Assert.assertNull(display.getDescription());
+ Assert.assertNull(display.getBackgroundColor());
+ Assert.assertNull(display.getTextColor());
+ Assert.assertEquals(display.getBackgroundImage().getUri(), "https://example.org/bg.png");
+ Assert.assertNull(display.getBackgroundImage().getAltText());
+
+ ClaimDescription claim = configuration.getCredentialMetadata().getClaims().get(0);
+ Assert.assertEquals(claim.getPath().get(0), "given_name");
+ Assert.assertNull(claim.getDisplay());
+ Assert.assertNull(claim.getMandatory());
+ }
+
+ @Test(expectedExceptions = JsonProcessingException.class)
+ public void parseSDJWTMissingVctThrows() throws JsonProcessingException {
+ CredentialConfiguration.parse("{\"format\":\"dc+sd-jwt\"}");
+ }
+
+ @Test
+ public void parseVCSDJWT() throws JsonProcessingException {
+ String json = "{\"format\":\"vc+sd-jwt\","
+ + "\"credential_definition\":{\"@context\":[\"https://example.org/ctx\"],"
+ + "\"type\":[\"VerifiableCredential\",\"IdentityCredential\"],"
+ + "\"issuer\":\"https://issuer.example.org\"}}";
+ CredentialConfiguration configuration = CredentialConfiguration.parse(json);
+ Assert.assertEquals(configuration.getFormat(), "vc+sd-jwt");
+ Assert.assertNotNull(configuration.getCredentialDefinition());
+ Assert.assertEquals(configuration.getCredentialDefinition().getType().get(1), "IdentityCredential");
+ Assert.assertEquals(configuration.getCredentialDefinition().getIssuer(), "https://issuer.example.org");
+ Assert.assertNull(configuration.getVct());
+ }
+
+ @Test(expectedExceptions = JsonProcessingException.class)
+ public void parseVCSDJWTMissingCredentialDefinitionThrows() throws JsonProcessingException {
+ CredentialConfiguration.parse("{\"format\":\"vc+sd-jwt\"}");
}
}
\ No newline at end of file
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/ValidateCredentialOffering.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/ValidateCredentialOffering.java
index 43ef451..5e071d7 100644
--- a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/ValidateCredentialOffering.java
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/ValidateCredentialOffering.java
@@ -16,12 +16,19 @@
package org.geant.shibboleth.plugin.openidvci.profile.impl;
+import java.util.List;
+import java.util.Map;
import java.util.Set;
import javax.annotation.Nonnull;
+import org.geant.shibboleth.plugin.openidvci.credential.ClaimDescription;
+import org.geant.shibboleth.plugin.openidvci.credential.CredentialConfiguration;
+import org.geant.shibboleth.plugin.openidvci.credential.CredentialMetadata;
import org.geant.shibboleth.plugin.openidvci.messaging.context.CredentialOfferContext;
import org.geant.shibboleth.plugin.openidvci.messaging.impl.CredentialOfferRequest;
+import org.geant.shibboleth.plugin.openidvci.messaging.impl.CredentialOfferRequestedClaim;
+import org.geant.shibboleth.plugin.openidvci.messaging.impl.CredentialOfferRequestedCredential;
import org.geant.shibboleth.plugin.openidvci.profile.OpenIDVCIEventIds;
import org.opensaml.profile.action.ActionSupport;
import org.opensaml.profile.context.ProfileRequestContext;
@@ -31,14 +38,12 @@ import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
/**
* Action validates that there is a credential offering matching the request.
- * For both pre-authorized and code flows we check only that credential
- * configuration id is a supported one. If validation is successful a
- * {@link CredentialOfferContext} is created and requested credentials and
- * possible tx code is stored there.
- *
- * TBD: verify that also claims in pre-authorized request match the claims in
- * supported configuration.
- *
+ * For both pre-authorized and code flows the credential configuration id must
+ * be a supported one. Additionally, for the pre-authorized flow, every claim
+ * marked {@code mandatory: true} in the configuration's
+ * {@code credential_metadata.claims} must be present in the requested claims.
+ * If validation is successful a {@link CredentialOfferContext} is created and
+ * requested credentials and possible tx code is stored there.
*/
public class ValidateCredentialOffering extends AbstractCredentialValidationAction {
@@ -86,6 +91,22 @@ public class ValidateCredentialOffering extends AbstractCredentialValidationActi
}
});
+ if (request.getPreAuthorizedCredentials() != null) {
+ for (Map.Entry<String, CredentialOfferRequestedCredential> entry : request.getPreAuthorizedCredentials()
+ .entrySet()) {
+ final CredentialConfiguration configuration = getCredentialConfigurations().get(entry.getKey());
+ if (configuration == null) {
+ continue;
+ }
+ if (!hasAllMandatoryClaims(configuration, entry.getValue())) {
+ log.error("{} Requested credential {} is missing one or more mandatory claims", getLogPrefix(),
+ entry.getKey());
+ ActionSupport.buildEvent(profileRequestContext, OpenIDVCIEventIds.NO_CREDENTIALS_REQUEST);
+ return;
+ }
+ }
+ }
+
CredentialOfferContext ctx = new CredentialOfferContext();
ctx.setCredentialConfigurations(getCredentialConfigurations());
ctx.setValidatedPreAuthorizedCredentials(request.getPreAuthorizedCredentials());
@@ -93,4 +114,62 @@ public class ValidateCredentialOffering extends AbstractCredentialValidationActi
ctx.setCredentialOfferTxCode(request.getCredentialOfferTxCode());
profileRequestContext.getInboundMessageContext().addSubcontext(ctx, true);
}
+
+ /**
+ * Check that every {@code mandatory: true} claim declared in the configuration
+ * is present in the requested claims.
+ *
+ * @param configuration Credential configuration for the requested id
+ * @param requestedCredential Requested claims from the pre-authorized offer
+ * @return true if all mandatory claims are present, or if the configuration
+ * declares none
+ */
+ private boolean hasAllMandatoryClaims(@Nonnull final CredentialConfiguration configuration,
+ @Nonnull final CredentialOfferRequestedCredential requestedCredential) {
+ final CredentialMetadata metadata = configuration.getCredentialMetadata();
+ if (metadata == null || metadata.getClaims() == null) {
+ return true;
+ }
+ for (ClaimDescription claim : metadata.getClaims()) {
+ if (!Boolean.TRUE.equals(claim.getMandatory())) {
+ continue;
+ }
+ if (!requestContainsPath(requestedCredential, claim.getPath())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Test whether any requested claim's path equals the given configuration path.
+ * Configuration paths are typed as {@link Object} per spec (element may be
+ * string, non-negative integer, or null); this comparison only matches string
+ * elements against the request's string-only path.
+ *
+ * @param requestedCredential Requested claims from the pre-authorized offer
+ * @param configPath Path from a configuration claim
+ * @return true if a matching requested claim path exists
+ */
+ private boolean requestContainsPath(@Nonnull final CredentialOfferRequestedCredential requestedCredential,
+ @Nonnull final List<Object> configPath) {
+ for (CredentialOfferRequestedClaim requestedClaim : requestedCredential.getRequestedCredential()) {
+ final List<String> requestPath = requestedClaim.getPath();
+ if (configPath.size() != requestPath.size()) {
+ continue;
+ }
+ boolean match = true;
+ for (int i = 0; i < configPath.size(); i++) {
+ final Object configElement = configPath.get(i);
+ if (!(configElement instanceof String) || !configElement.equals(requestPath.get(i))) {
+ match = false;
+ break;
+ }
+ }
+ if (match) {
+ return true;
+ }
+ }
+ return false;
+ }
}
\ No newline at end of file
diff --git a/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/profile/impl/ValidateCredentialOfferingTest.java b/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/profile/impl/ValidateCredentialOfferingTest.java
index 58dafa2..6e9c066 100644
--- a/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/profile/impl/ValidateCredentialOfferingTest.java
+++ b/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/profile/impl/ValidateCredentialOfferingTest.java
@@ -1,5 +1,5 @@
/*
-preAuthorizedCredentials * Copyright (c) 2025, GÉANT
+ * 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
@@ -104,4 +104,71 @@ public class ValidateCredentialOfferingTest {
ActionTestingSupport.assertEvent(action.execute(requestCtx), OpenIDVCIEventIds.NO_CREDENTIAL_CONFIGURATION);
}
+ @Test
+ public void testMissingMandatoryClaim() throws Exception {
+ HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, new URI("http://example.com"));
+ httpRequest.setAuthorization("Basic dGVzdDp0ZXN0");
+ httpRequest.setContentType("application/json");
+ // Request omits the mandatory "mail" claim declared in the configuration.
+ httpRequest.setQuery(
+ "{\"preAuthorizedCredentials\":{\"GeantIncubatorDiploma\":[{\"path\":[\"givenName\"],\"value\":\"Gemma\"},{\"path\":[\"familyName\"],\"value\":\"Erasmus\"}]}}");
+ profileRequestCtx.getInboundMessageContext().setMessage(CredentialOfferRequest.parse(httpRequest));
+ ActionTestingSupport.assertEvent(action.execute(requestCtx), OpenIDVCIEventIds.NO_CREDENTIALS_REQUEST);
+ }
+
+ @Test
+ public void testCodeFlowSuccess() throws Exception {
+ HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, new URI("http://example.com"));
+ httpRequest.setAuthorization("Basic dGVzdDp0ZXN0");
+ httpRequest.setContentType("application/json");
+ httpRequest.setQuery("{\"codeCredentials\":[\"GeantIncubatorDiploma\"]}");
+ profileRequestCtx.getInboundMessageContext().setMessage(CredentialOfferRequest.parse(httpRequest));
+ ActionTestingSupport.assertProceedEvent(action.execute(requestCtx));
+ CredentialOfferContext ctx = profileRequestCtx.getInboundMessageContext()
+ .getSubcontext(CredentialOfferContext.class);
+ Assert.notNull(ctx, "Must not be null");
+ Assert.notNull(ctx.getValidatedCodeCredentials(), "Must not be null");
+ }
+
+ @Test
+ public void testSuccessVcSdJwt() throws Exception {
+ HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, new URI("http://example.com"));
+ httpRequest.setAuthorization("Basic dGVzdDp0ZXN0");
+ httpRequest.setContentType("application/json");
+ httpRequest.setQuery(
+ "{\"preAuthorizedCredentials\":{\"GeantIncubatorDiploma2\":[{\"path\":[\"credentialSubject\",\"mail\"],\"value\":\"incubatorUser at example.org\"},{\"path\":[\"credentialSubject\",\"givenName\"],\"value\":\"Gemma\"},{\"path\":[\"credentialSubject\",\"familyName\"],\"value\":\"Erasmus\"}]}}");
+ profileRequestCtx.getInboundMessageContext().setMessage(CredentialOfferRequest.parse(httpRequest));
+ ActionTestingSupport.assertProceedEvent(action.execute(requestCtx));
+ CredentialOfferContext ctx = profileRequestCtx.getInboundMessageContext()
+ .getSubcontext(CredentialOfferContext.class);
+ Assert.notNull(ctx, "Must not be null");
+ Assert.notNull(ctx.getCredentialConfigurations(), "Must not be null");
+ Assert.notNull(ctx.getValidatedPreAuthorizedCredentials(), "Must not be null");
+ }
+
+ @Test
+ public void testMissingMandatoryClaimVcSdJwt() throws Exception {
+ HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, new URI("http://example.com"));
+ httpRequest.setAuthorization("Basic dGVzdDp0ZXN0");
+ httpRequest.setContentType("application/json");
+ httpRequest.setQuery(
+ "{\"preAuthorizedCredentials\":{\"GeantIncubatorDiploma2\":[{\"path\":[\"credentialSubject\",\"givenName\"],\"value\":\"Gemma\"},{\"path\":[\"credentialSubject\",\"familyName\"],\"value\":\"Erasmus\"}]}}");
+ profileRequestCtx.getInboundMessageContext().setMessage(CredentialOfferRequest.parse(httpRequest));
+ ActionTestingSupport.assertEvent(action.execute(requestCtx), OpenIDVCIEventIds.NO_CREDENTIALS_REQUEST);
+ }
+
+ @Test
+ public void testCodeFlowSuccessVcSdJwt() throws Exception {
+ HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, new URI("http://example.com"));
+ httpRequest.setAuthorization("Basic dGVzdDp0ZXN0");
+ httpRequest.setContentType("application/json");
+ httpRequest.setQuery("{\"codeCredentials\":[\"GeantIncubatorDiploma2\"]}");
+ profileRequestCtx.getInboundMessageContext().setMessage(CredentialOfferRequest.parse(httpRequest));
+ ActionTestingSupport.assertProceedEvent(action.execute(requestCtx));
+ CredentialOfferContext ctx = profileRequestCtx.getInboundMessageContext()
+ .getSubcontext(CredentialOfferContext.class);
+ Assert.notNull(ctx, "Must not be null");
+ Assert.notNull(ctx.getValidatedCodeCredentials(), "Must not be null");
+ }
+
}
\ No newline at end of file
diff --git a/openid-vci-impl/src/test/resources/conf/verifiable-credentials.json b/openid-vci-impl/src/test/resources/conf/verifiable-credentials.json
index 1d57042..64f5740 100644
--- a/openid-vci-impl/src/test/resources/conf/verifiable-credentials.json
+++ b/openid-vci-impl/src/test/resources/conf/verifiable-credentials.json
@@ -12,48 +12,50 @@
]
}
},
- "display": [
- {
- "name": "GeantIncubatorCredential",
- "description": "Card Credential",
- "background_color": "rgba(0, 0, 0, 0.2)",
- "text_color": "#FBFBFB",
- "logo": {
- "url": "https://resources.geant.org/wp-content/uploads/2022/02/GEANT_logo_lo_res.jpg",
- "alt_text": "Geant Incubator logo"
- }
- },
- {
- "locale": "en-EN",
- "name": "GeantIncubatorCredential",
- "description": "Geant Incubator statement",
- "background_color": "rgba(0, 0, 0, 0.2)",
- "text_color": "#FBFBFB",
- "logo": {
- "url": "https://resources.geant.org/wp-content/uploads/2022/02/GEANT_logo_lo_res.jpg",
- "alt_text": "Geant Incubator logo"
- }
- },
- {
- "locale": "fi-FI",
- "name": "GeantIncubatorCredential",
- "description": "Geant Incubator lausuma",
- "background_color": "rgba(0, 0, 0, 0.2)",
- "text_color": "#FBFBFB",
- "logo": {
- "url": "https://resources.geant.org/wp-content/uploads/2022/02/GEANT_logo_lo_res.jpg",
- "alt_text": "Geant Incubator logo"
- }
- }
- ],
"vct": "GeantIncubatorDiploma",
- "claims": [
- {"path": ["mail"],"display":[{"name":"E-mail","locale":"en-EN"}]},
- {"path": ["eppn"],"display":[{"name":"Principal name","locale":"en-EN"}]},
- {"path": ["givenName"],"display":[{"name":"CN","locale":"en-EN"}]},
- {"path": ["familyName"],"display":[{"name":"SN","locale":"en-EN"}]},
- {"path": ["affiliation"],"display":[{"name":"Affiliation","locale":"en-EN"}]}
- ]
+ "credential_metadata": {
+ "display": [
+ {
+ "name": "GeantIncubatorCredential",
+ "description": "Card Credential",
+ "background_color": "rgba(0, 0, 0, 0.2)",
+ "text_color": "#FBFBFB",
+ "logo": {
+ "uri": "https://resources.geant.org/wp-content/uploads/2022/02/GEANT_logo_lo_res.jpg",
+ "alt_text": "Geant Incubator logo"
+ }
+ },
+ {
+ "locale": "en-EN",
+ "name": "GeantIncubatorCredential",
+ "description": "Geant Incubator statement",
+ "background_color": "rgba(0, 0, 0, 0.2)",
+ "text_color": "#FBFBFB",
+ "logo": {
+ "uri": "https://resources.geant.org/wp-content/uploads/2022/02/GEANT_logo_lo_res.jpg",
+ "alt_text": "Geant Incubator logo"
+ }
+ },
+ {
+ "locale": "fi-FI",
+ "name": "GeantIncubatorCredential",
+ "description": "Geant Incubator lausuma",
+ "background_color": "rgba(0, 0, 0, 0.2)",
+ "text_color": "#FBFBFB",
+ "logo": {
+ "uri": "https://resources.geant.org/wp-content/uploads/2022/02/GEANT_logo_lo_res.jpg",
+ "alt_text": "Geant Incubator logo"
+ }
+ }
+ ],
+ "claims": [
+ {"path": ["mail"], "mandatory": true, "display":[{"name":"E-mail","locale":"en-EN"}]},
+ {"path": ["eppn"], "display":[{"name":"Principal name","locale":"en-EN"}]},
+ {"path": ["givenName"], "display":[{"name":"CN","locale":"en-EN"}]},
+ {"path": ["familyName"], "display":[{"name":"SN","locale":"en-EN"}]},
+ {"path": ["affiliation"], "display":[{"name":"Affiliation","locale":"en-EN"}]}
+ ]
+ }
},
"GeantIncubatorDiploma2": {
"format": "vc+sd-jwt",
@@ -67,48 +69,54 @@
]
}
},
- "display": [
- {
- "name": "GeantIncubatorCredential2",
- "description": "Card Credential",
- "background_color": "rgba(0, 0, 0, 0.2)",
- "text_color": "#FBFBFB",
- "logo": {
- "url": "https://resources.geant.org/wp-content/uploads/2022/02/GEANT_logo_lo_res.jpg",
- "alt_text": "Geant Incubator logo"
- }
- },
- {
- "locale": "en-EN",
- "name": "GeantIncubatorCredential2",
- "description": "Geant Incubator statement",
- "background_color": "rgba(0, 0, 0, 0.2)",
- "text_color": "#FBFBFB",
- "logo": {
- "url": "https://resources.geant.org/wp-content/uploads/2022/02/GEANT_logo_lo_res.jpg",
- "alt_text": "Geant Incubator logo"
- }
- },
- {
- "locale": "fi-FI",
- "name": "GeantIncubatorCredential2",
- "description": "Geant Incubator lausuma",
- "background_color": "rgba(0, 0, 0, 0.2)",
- "text_color": "#FBFBFB",
- "logo": {
- "url": "https://resources.geant.org/wp-content/uploads/2022/02/GEANT_logo_lo_res.jpg",
- "alt_text": "Geant Incubator logo"
+ "credential_definition": {
+ "@context": ["https://www.w3.org/2018/credentials/v1"],
+ "type": ["VerifiableCredential", "GeantIncubatorDiploma2"],
+ "issuer": "https://issuer.example.org"
+ },
+ "credential_metadata": {
+ "display": [
+ {
+ "name": "GeantIncubatorCredential2",
+ "description": "Card Credential",
+ "background_color": "rgba(0, 0, 0, 0.2)",
+ "text_color": "#FBFBFB",
+ "logo": {
+ "uri": "https://resources.geant.org/wp-content/uploads/2022/02/GEANT_logo_lo_res.jpg",
+ "alt_text": "Geant Incubator logo"
+ }
+ },
+ {
+ "locale": "en-EN",
+ "name": "GeantIncubatorCredential2",
+ "description": "Geant Incubator statement",
+ "background_color": "rgba(0, 0, 0, 0.2)",
+ "text_color": "#FBFBFB",
+ "logo": {
+ "uri": "https://resources.geant.org/wp-content/uploads/2022/02/GEANT_logo_lo_res.jpg",
+ "alt_text": "Geant Incubator logo"
+ }
+ },
+ {
+ "locale": "fi-FI",
+ "name": "GeantIncubatorCredential2",
+ "description": "Geant Incubator lausuma",
+ "background_color": "rgba(0, 0, 0, 0.2)",
+ "text_color": "#FBFBFB",
+ "logo": {
+ "uri": "https://resources.geant.org/wp-content/uploads/2022/02/GEANT_logo_lo_res.jpg",
+ "alt_text": "Geant Incubator logo"
+ }
}
- }
- ],
- "vct": "GeantIncubatorDiploma2",
- "claims": [
- {"path": ["mail"],"display":[{"name":"E-mail","locale":"en-EN"}]},
- {"path": ["eppn"],"display":[{"name":"Principal name","locale":"en-EN"}]},
- {"path": ["givenName"],"display":[{"name":"CN","locale":"en-EN"}]},
- {"path": ["familyName"],"display":[{"name":"SN","locale":"en-EN"}]},
- {"path": ["affiliation"],"display":[{"name":"Affiliation","locale":"en-EN"}]}
- ]
+ ],
+ "claims": [
+ {"path": ["credentialSubject", "mail"], "mandatory": true, "display":[{"name":"E-mail","locale":"en-EN"}]},
+ {"path": ["credentialSubject", "eppn"], "display":[{"name":"Principal name","locale":"en-EN"}]},
+ {"path": ["credentialSubject", "givenName"], "display":[{"name":"CN","locale":"en-EN"}]},
+ {"path": ["credentialSubject", "familyName"], "display":[{"name":"SN","locale":"en-EN"}]},
+ {"path": ["credentialSubject", "affiliation"], "display":[{"name":"Affiliation","locale":"en-EN"}]}
+ ]
+ }
}
}
-}
\ No newline at end of file
+}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list