diff --git a/services/src/main/java/org/keycloak/protocol/oid4vc/model/Claim.java b/services/src/main/java/org/keycloak/protocol/oid4vc/model/Claim.java
new file mode 100644
index 0000000000..e66c568729
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oid4vc/model/Claim.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2024 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 java.util.List;
+
+/**
+ * Holding metadata on a claim of verifiable credential.
+ *
+ * See: openid-4-verifiable-credential-issuance-1_0.html#appendix-A.2.2
+ *
+ * @author Francis Pouatcha
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class Claim {
+ @JsonProperty("mandatory")
+ private Boolean mandatory;
+ @JsonProperty("value_type")
+ private String valueType;
+ @JsonProperty("display")
+ private List display;
+
+ public Boolean getMandatory() {
+ return mandatory;
+ }
+
+ public Claim setMandatory(Boolean mandatory) {
+ this.mandatory = mandatory;
+ return this;
+ }
+
+ public String getValueType() {
+ return valueType;
+ }
+
+ public Claim setValueType(String valueType) {
+ this.valueType = valueType;
+ return this;
+ }
+
+ public List getDisplay() {
+ return display;
+ }
+
+ public Claim setDisplay(List display) {
+ this.display = display;
+ return this;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oid4vc/model/ClaimDisplay.java b/services/src/main/java/org/keycloak/protocol/oid4vc/model/ClaimDisplay.java
new file mode 100644
index 0000000000..83c9ca5a62
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oid4vc/model/ClaimDisplay.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2024 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;
+
+/**
+ *
+ * @author Francis Pouatcha
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class ClaimDisplay {
+ @JsonProperty("name")
+ private String name;
+
+ @JsonProperty("locale")
+ private String locale;
+
+ public String getName() {
+ return name;
+ }
+
+ public ClaimDisplay setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public String getLocale() {
+ return locale;
+ }
+
+ public ClaimDisplay setLocale(String locale) {
+ this.locale = locale;
+ return this;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oid4vc/model/Claims.java b/services/src/main/java/org/keycloak/protocol/oid4vc/model/Claims.java
new file mode 100644
index 0000000000..308295a2de
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oid4vc/model/Claims.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2024 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 org.keycloak.util.JsonSerialization;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+/**
+ * @author Francis Pouatcha
+ */
+public class Claims extends HashMap {
+
+ public String toJsonString(){
+ try {
+ return JsonSerialization.writeValueAsString(this);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static Claims fromJsonString(String jsonString){
+ try {
+ return JsonSerialization.readValue(jsonString, Claims.class);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oid4vc/model/DisplayObject.java b/services/src/main/java/org/keycloak/protocol/oid4vc/model/DisplayObject.java
index a25d8155d7..df51baf4ad 100644
--- a/services/src/main/java/org/keycloak/protocol/oid4vc/model/DisplayObject.java
+++ b/services/src/main/java/org/keycloak/protocol/oid4vc/model/DisplayObject.java
@@ -14,17 +14,15 @@
* 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.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
+import org.keycloak.util.JsonSerialization;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
+import java.io.IOException;
/**
* Represents a DisplayObject, as used in the OID4VCI Credentials Issuer Metadata
@@ -126,26 +124,20 @@ public class DisplayObject {
return this;
}
- public Map toDotNotation() {
- Map dotNotation = new HashMap<>();
- dotNotation.put(NAME_KEY, name);
- dotNotation.put(LOCALE_KEY, locale);
- dotNotation.put(LOGO_KEY, logo);
- dotNotation.put(DESCRIPTION_KEY, description);
- dotNotation.put(BG_COLOR_KEY, backgroundColor);
- dotNotation.put(TEXT_COLOR_KEY, textColor);
- return dotNotation;
+ public String toJsonString(){
+ try {
+ return JsonSerialization.writeValueAsString(this);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
}
- public static DisplayObject fromDotNotation(Map dotNotated) {
- DisplayObject displayObject = new DisplayObject();
- Optional.ofNullable(dotNotated.get(NAME_KEY)).ifPresent(displayObject::setName);
- Optional.ofNullable(dotNotated.get(LOCALE_KEY)).ifPresent(displayObject::setLocale);
- Optional.ofNullable(dotNotated.get(LOGO_KEY)).ifPresent(displayObject::setLogo);
- Optional.ofNullable(dotNotated.get(DESCRIPTION_KEY)).ifPresent(displayObject::setDescription);
- Optional.ofNullable(dotNotated.get(BG_COLOR_KEY)).ifPresent(displayObject::setBackgroundColor);
- Optional.ofNullable(dotNotated.get(TEXT_COLOR_KEY)).ifPresent(displayObject::setTextColor);
- return displayObject;
+ public static DisplayObject fromJsonString(String jsonString){
+ try {
+ return JsonSerialization.readValue(jsonString, DisplayObject.class);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
}
@Override
@@ -173,4 +165,4 @@ public class DisplayObject {
result = 31 * result + (getTextColor() != null ? getTextColor().hashCode() : 0);
return result;
}
-}
\ No newline at end of file
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oid4vc/model/ProofTypeCWT.java b/services/src/main/java/org/keycloak/protocol/oid4vc/model/ProofTypeCWT.java
new file mode 100644
index 0000000000..dd06b1a03b
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oid4vc/model/ProofTypeCWT.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2024 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 java.util.List;
+import java.util.Objects;
+
+/**
+ * See: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-cwt-proof-type
+ *
+ * @author Francis Pouatcha
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class ProofTypeCWT {
+
+ @JsonProperty("proof_signing_alg_values_supported")
+ private List proofSigningAlgValuesSupported;
+
+ @JsonProperty("proof_alg_values_supported")
+ private List proofAlgValuesSupported;
+
+ @JsonProperty("proof_crv_values_supported")
+ private List proofCrvValuesSupported;
+
+ public List getProofSigningAlgValuesSupported() {
+ return proofSigningAlgValuesSupported;
+ }
+
+ public ProofTypeCWT setProofSigningAlgValuesSupported(List proofSigningAlgValuesSupported) {
+ this.proofSigningAlgValuesSupported = proofSigningAlgValuesSupported;
+ return this;
+ }
+
+ public List getProofAlgValuesSupported() {
+ return proofAlgValuesSupported;
+ }
+
+ public ProofTypeCWT setProofAlgValuesSupported(List proofAlgValuesSupported) {
+ this.proofAlgValuesSupported = proofAlgValuesSupported;
+ return this;
+ }
+
+ public List getProofCrvValuesSupported() {
+ return proofCrvValuesSupported;
+ }
+
+ public ProofTypeCWT setProofCrvValuesSupported(List proofCrvValuesSupported) {
+ this.proofCrvValuesSupported = proofCrvValuesSupported;
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ProofTypeCWT that = (ProofTypeCWT) o;
+ return Objects.equals(proofSigningAlgValuesSupported, that.proofSigningAlgValuesSupported) && Objects.equals(proofAlgValuesSupported, that.proofAlgValuesSupported) && Objects.equals(proofCrvValuesSupported, that.proofCrvValuesSupported);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(proofSigningAlgValuesSupported, proofAlgValuesSupported, proofCrvValuesSupported);
+ }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oid4vc/model/ProofTypeJWT.java b/services/src/main/java/org/keycloak/protocol/oid4vc/model/ProofTypeJWT.java
new file mode 100644
index 0000000000..cf923e37fc
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oid4vc/model/ProofTypeJWT.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2024 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 java.util.List;
+import java.util.Objects;
+
+/**
+ * See: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-jwt-proof-type
+ *
+ * @author Francis Pouatcha
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class ProofTypeJWT {
+ @JsonProperty("proof_signing_alg_values_supported")
+ private List proofSigningAlgValuesSupported;
+
+ public List getProofSigningAlgValuesSupported() {
+ return proofSigningAlgValuesSupported;
+ }
+
+ public ProofTypeJWT setProofSigningAlgValuesSupported(List proofSigningAlgValuesSupported) {
+ this.proofSigningAlgValuesSupported = proofSigningAlgValuesSupported;
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ProofTypeJWT that = (ProofTypeJWT) o;
+ return Objects.equals(proofSigningAlgValuesSupported, that.proofSigningAlgValuesSupported);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(proofSigningAlgValuesSupported);
+ }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oid4vc/model/ProofTypeLdpVp.java b/services/src/main/java/org/keycloak/protocol/oid4vc/model/ProofTypeLdpVp.java
new file mode 100644
index 0000000000..7de5c83a9f
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oid4vc/model/ProofTypeLdpVp.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2024 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;
+
+/**
+ * See: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-ldp_vp-proof-type
+ *
+ * @author Francis Pouatcha
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class ProofTypeLdpVp {
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oid4vc/model/ProofTypesSupported.java b/services/src/main/java/org/keycloak/protocol/oid4vc/model/ProofTypesSupported.java
new file mode 100644
index 0000000000..d232ff35ee
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oid4vc/model/ProofTypesSupported.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2024 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.util.JsonSerialization;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * See: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-proof-types
+ *
+ * @author Francis Pouatcha
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class ProofTypesSupported {
+ @JsonProperty("jwt")
+ private ProofTypeJWT jwt;
+ @JsonProperty("cwt")
+ private ProofTypeCWT cwt;
+
+ @JsonProperty("ldp_vp")
+ private ProofTypeLdpVp ldpVp;
+
+ public ProofTypeJWT getJwt() {
+ return jwt;
+ }
+
+ public ProofTypesSupported setJwt(ProofTypeJWT jwt) {
+ this.jwt = jwt;
+ return this;
+ }
+
+ public ProofTypeCWT getCwt() {
+ return cwt;
+ }
+
+ public ProofTypesSupported setCwt(ProofTypeCWT cwt) {
+ this.cwt = cwt;
+ return this;
+ }
+
+ public ProofTypeLdpVp getLdpVp() {
+ return ldpVp;
+ }
+
+ public ProofTypesSupported setLdpVp(ProofTypeLdpVp ldpVp) {
+ this.ldpVp = ldpVp;
+ return this;
+ }
+
+ public String toJsonString(){
+ try {
+ return JsonSerialization.writeValueAsString(this);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static ProofTypesSupported fromJsonString(String jsonString){
+ try {
+ return JsonSerialization.readValue(jsonString, ProofTypesSupported.class);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ProofTypesSupported that = (ProofTypesSupported) o;
+ return Objects.equals(jwt, that.jwt) && Objects.equals(cwt, that.cwt) && Objects.equals(ldpVp, that.ldpVp);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(jwt, cwt, ldpVp);
+ }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oid4vc/model/SupportedCredentialConfiguration.java b/services/src/main/java/org/keycloak/protocol/oid4vc/model/SupportedCredentialConfiguration.java
index c15b8369cc..bef34c1b88 100644
--- a/services/src/main/java/org/keycloak/protocol/oid4vc/model/SupportedCredentialConfiguration.java
+++ b/services/src/main/java/org/keycloak/protocol/oid4vc/model/SupportedCredentialConfiguration.java
@@ -14,7 +14,6 @@
* 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.JsonIgnore;
@@ -26,7 +25,9 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
+import java.util.stream.Collectors;
/**
* A supported credential, as used in the Credentials Issuer Metadata in OID4VCI
@@ -42,13 +43,19 @@ public class SupportedCredentialConfiguration {
@JsonIgnore
private static final String SCOPE_KEY = "scope";
@JsonIgnore
- private static final String CRYPTOGRAPHIC_BINDING_METHODS_SUPPORTED_KEY = " credential_signing_alg_values_supported";
+ private static final String CRYPTOGRAPHIC_BINDING_METHODS_SUPPORTED_KEY = "cryptographic_binding_methods_supported";
@JsonIgnore
private static final String CRYPTOGRAPHIC_SUITES_SUPPORTED_KEY = "cryptographic_suites_supported";
@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
+ private static final String VERIFIABLE_CREDENTIAL_TYPE_KEY = "vct";
private String id;
@JsonProperty(FORMAT_KEY)
@@ -67,7 +74,16 @@ public class SupportedCredentialConfiguration {
private List credentialSigningAlgValuesSupported;
@JsonProperty(DISPLAY_KEY)
- private DisplayObject display;
+ private List display;
+
+ @JsonProperty(VERIFIABLE_CREDENTIAL_TYPE_KEY)
+ private String vct;
+
+ @JsonProperty(PROOF_TYPES_SUPPORTED_KEY)
+ private ProofTypesSupported proofTypesSupported;
+
+ @JsonProperty(CLAIMS_KEY)
+ private Claims claims;
public Format getFormat() {
return format;
@@ -105,11 +121,11 @@ public class SupportedCredentialConfiguration {
return this;
}
- public DisplayObject getDisplay() {
+ public List getDisplay() {
return display;
}
- public SupportedCredentialConfiguration setDisplay(DisplayObject display) {
+ public SupportedCredentialConfiguration setDisplay(List display) {
this.display = display;
return this;
}
@@ -135,9 +151,37 @@ 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;
+ }
+
+ public SupportedCredentialConfiguration setVct(String vct) {
+ this.vct = vct;
+ return this;
+ }
+
+ public ProofTypesSupported getProofTypesSupported() {
+ return proofTypesSupported;
+ }
+
+ public SupportedCredentialConfiguration setProofTypesSupported(ProofTypesSupported proofTypesSupported) {
+ this.proofTypesSupported = proofTypesSupported;
+ return this;
+ }
+
public Map toDotNotation() {
Map dotNotation = new HashMap<>();
Optional.ofNullable(format).ifPresent(format -> dotNotation.put(id + DOT_SEPARATOR + FORMAT_KEY, format.toString()));
+ Optional.ofNullable(vct).ifPresent(vct -> dotNotation.put(id + DOT_SEPARATOR + VERIFIABLE_CREDENTIAL_TYPE_KEY, vct));
Optional.ofNullable(scope).ifPresent(scope -> dotNotation.put(id + DOT_SEPARATOR + SCOPE_KEY, scope));
Optional.ofNullable(cryptographicBindingMethodsSupported).ifPresent(types ->
dotNotation.put(id + DOT_SEPARATOR + CRYPTOGRAPHIC_BINDING_METHODS_SUPPORTED_KEY, String.join(",", cryptographicBindingMethodsSupported)));
@@ -145,13 +189,16 @@ public class SupportedCredentialConfiguration {
dotNotation.put(id + DOT_SEPARATOR + CRYPTOGRAPHIC_SUITES_SUPPORTED_KEY, String.join(",", cryptographicSuitesSupported)));
Optional.ofNullable(cryptographicSuitesSupported).ifPresent(types ->
dotNotation.put(id + DOT_SEPARATOR + CREDENTIAL_SIGNING_ALG_VALUES_SUPPORTED_KEY, String.join(",", credentialSigningAlgValuesSupported)));
+ Optional.ofNullable(claims).ifPresent(c -> dotNotation.put(id + DOT_SEPARATOR + CLAIMS_KEY, c.toJsonString()));
+
+ Optional.ofNullable(display)
+ .ifPresent(d -> d.stream()
+ .filter(Objects::nonNull)
+ .forEach(o -> dotNotation.put(id + DOT_SEPARATOR + DISPLAY_KEY + DOT_SEPARATOR + d.indexOf(o), o.toJsonString())));
+
+ Optional.ofNullable(proofTypesSupported)
+ .ifPresent(p -> dotNotation.put(id + DOT_SEPARATOR + PROOF_TYPES_SUPPORTED_KEY, p.toJsonString()));
- Map dotNotatedDisplay = Optional.ofNullable(display)
- .map(DisplayObject::toDotNotation)
- .orElse(Map.of());
- dotNotatedDisplay.entrySet().stream()
- .filter(entry -> entry.getValue() != null)
- .forEach(entry -> dotNotation.put(id + DOT_SEPARATOR + DISPLAY_KEY + "." + entry.getKey(), entry.getValue()));
return dotNotation;
}
@@ -159,6 +206,7 @@ public class SupportedCredentialConfiguration {
SupportedCredentialConfiguration supportedCredentialConfiguration = new SupportedCredentialConfiguration().setId(credentialId);
Optional.ofNullable(dotNotated.get(credentialId + DOT_SEPARATOR + FORMAT_KEY)).map(Format::fromString).ifPresent(supportedCredentialConfiguration::setFormat);
+ Optional.ofNullable(dotNotated.get(credentialId + DOT_SEPARATOR + VERIFIABLE_CREDENTIAL_TYPE_KEY)).ifPresent(supportedCredentialConfiguration::setVct);
Optional.ofNullable(dotNotated.get(credentialId + DOT_SEPARATOR + SCOPE_KEY)).ifPresent(supportedCredentialConfiguration::setScope);
Optional.ofNullable(dotNotated.get(credentialId + DOT_SEPARATOR + CRYPTOGRAPHIC_BINDING_METHODS_SUPPORTED_KEY))
.map(cbms -> cbms.split(","))
@@ -172,45 +220,38 @@ public class SupportedCredentialConfiguration {
.map(css -> css.split(","))
.map(Arrays::asList)
.ifPresent(supportedCredentialConfiguration::setCredentialSigningAlgValuesSupported);
- Map displayMap = new HashMap<>();
- dotNotated.entrySet().forEach(entry -> {
- String key = entry.getKey();
- if (key.startsWith(credentialId + DOT_SEPARATOR + DISPLAY_KEY)) {
- displayMap.put(key.substring((credentialId + DOT_SEPARATOR + DISPLAY_KEY).length() + 1), entry.getValue());
- }
- });
- if (!displayMap.isEmpty()) {
- supportedCredentialConfiguration.setDisplay(DisplayObject.fromDotNotation(displayMap));
+ Optional.ofNullable(dotNotated.get(credentialId + DOT_SEPARATOR + CLAIMS_KEY))
+ .map(Claims::fromJsonString)
+ .ifPresent(supportedCredentialConfiguration::setClaims);
+
+ String displayKeyPrefix = credentialId + DOT_SEPARATOR + DISPLAY_KEY + DOT_SEPARATOR;
+ List displayList = dotNotated.entrySet().stream()
+ .filter(entry -> entry.getKey().startsWith(displayKeyPrefix))
+ .sorted(Map.Entry.comparingByKey())
+ .map(entry -> DisplayObject.fromJsonString(entry.getValue()))
+ .collect(Collectors.toList());
+
+ if(!displayList.isEmpty()){
+ supportedCredentialConfiguration.setDisplay(displayList);
}
+
+ Optional.ofNullable(dotNotated.get(credentialId + DOT_SEPARATOR + PROOF_TYPES_SUPPORTED_KEY))
+ .map(ProofTypesSupported::fromJsonString)
+ .ifPresent(supportedCredentialConfiguration::setProofTypesSupported);
+
return supportedCredentialConfiguration;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
- if (!(o instanceof SupportedCredentialConfiguration that)) return false;
-
- if (getId() != null ? !getId().equals(that.getId()) : that.getId() != null) return false;
- if (getFormat() != that.getFormat()) return false;
- if (getScope() != null ? !getScope().equals(that.getScope()) : that.getScope() != null) return false;
- if (getCryptographicBindingMethodsSupported() != null ? !getCryptographicBindingMethodsSupported().equals(that.getCryptographicBindingMethodsSupported()) : that.getCryptographicBindingMethodsSupported() != null)
- return false;
- if (getCryptographicSuitesSupported() != null ? !getCryptographicSuitesSupported().equals(that.getCryptographicSuitesSupported()) : that.getCryptographicSuitesSupported() != null)
- return false;
- if (getCredentialSigningAlgValuesSupported() != null ? !getCredentialSigningAlgValuesSupported().equals(that.getCredentialSigningAlgValuesSupported()) : that.getCredentialSigningAlgValuesSupported() != null)
- return false;
- return getDisplay() != null ? getDisplay().equals(that.getDisplay()) : that.getDisplay() == null;
+ if (o == null || getClass() != o.getClass()) return false;
+ SupportedCredentialConfiguration that = (SupportedCredentialConfiguration) o;
+ return Objects.equals(id, that.id) && format == that.format && Objects.equals(scope, that.scope) && Objects.equals(cryptographicBindingMethodsSupported, that.cryptographicBindingMethodsSupported) && Objects.equals(cryptographicSuitesSupported, that.cryptographicSuitesSupported) && Objects.equals(credentialSigningAlgValuesSupported, that.credentialSigningAlgValuesSupported) && Objects.equals(display, that.display) && Objects.equals(vct, that.vct) && Objects.equals(proofTypesSupported, that.proofTypesSupported) && Objects.equals(claims, that.claims);
}
@Override
public int hashCode() {
- int result = getId() != null ? getId().hashCode() : 0;
- result = 31 * result + (getFormat() != null ? getFormat().hashCode() : 0);
- result = 31 * result + (getScope() != null ? getScope().hashCode() : 0);
- result = 31 * result + (getCryptographicBindingMethodsSupported() != null ? getCryptographicBindingMethodsSupported().hashCode() : 0);
- result = 31 * result + (getCryptographicSuitesSupported() != null ? getCryptographicSuitesSupported().hashCode() : 0);
- result = 31 * result + (getCredentialSigningAlgValuesSupported() != null ? getCredentialSigningAlgValuesSupported().hashCode() : 0);
- result = 31 * result + (getDisplay() != null ? getDisplay().hashCode() : 0);
- return result;
+ return Objects.hash(id, format, scope, cryptographicBindingMethodsSupported, cryptographicSuitesSupported, credentialSigningAlgValuesSupported, display, vct, proofTypesSupported, claims);
}
-}
\ No newline at end of file
+}
diff --git a/services/src/test/java/org/keycloak/protocol/oid4vc/OID4VCClientRegistrationProviderTest.java b/services/src/test/java/org/keycloak/protocol/oid4vc/OID4VCClientRegistrationProviderTest.java
index 29736ffd18..b6caeff739 100644
--- a/services/src/test/java/org/keycloak/protocol/oid4vc/OID4VCClientRegistrationProviderTest.java
+++ b/services/src/test/java/org/keycloak/protocol/oid4vc/OID4VCClientRegistrationProviderTest.java
@@ -14,7 +14,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package org.keycloak.protocol.oid4vc;
import org.junit.Test;
@@ -23,6 +22,8 @@ import org.junit.runners.Parameterized;
import org.keycloak.protocol.oid4vc.model.DisplayObject;
import org.keycloak.protocol.oid4vc.model.Format;
import org.keycloak.protocol.oid4vc.model.OID4VCClient;
+import org.keycloak.protocol.oid4vc.model.ProofTypeJWT;
+import org.keycloak.protocol.oid4vc.model.ProofTypesSupported;
import org.keycloak.protocol.oid4vc.model.SupportedCredentialConfiguration;
import java.util.Arrays;
@@ -31,7 +32,6 @@ import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
@RunWith(Parameterized.class)
public class OID4VCClientRegistrationProviderTest {
@@ -68,13 +68,12 @@ public class OID4VCClientRegistrationProviderTest {
Map.of(
"vc.credential-id.format", Format.JWT_VC.toString(),
"vc.credential-id.scope", "AnotherCredential",
- "vc.credential-id.display.name", "Another",
- "vc.credential-id.display.locale", "en"),
+ "vc.credential-id.display.0", "{\"name\":\"Another\",\"locale\":\"en\"}"),
new OID4VCClient(null, "did:web:test.org",
List.of(new SupportedCredentialConfiguration()
.setId("credential-id")
.setFormat(Format.JWT_VC)
- .setDisplay(new DisplayObject().setLocale("en").setName("Another"))
+ .setDisplay(Arrays.asList(new DisplayObject().setLocale("en").setName("Another")))
.setScope("AnotherCredential")),
null, null)
},
@@ -83,23 +82,23 @@ public class OID4VCClientRegistrationProviderTest {
Map.of(
"vc.first-id.format", Format.JWT_VC.toString(),
"vc.first-id.scope", "AnotherCredential",
- "vc.first-id.display.name", "First",
- "vc.first-id.display.locale", "en",
+ "vc.first-id.display.0", "{\"name\":\"First\",\"locale\":\"en\"}",
"vc.second-id.format", Format.SD_JWT_VC.toString(),
"vc.second-id.scope", "MyType",
- "vc.second-id.display.name", "Second Credential",
- "vc.second-id.display.locale", "de"),
+ "vc.second-id.display.0", "{\"name\":\"Second Credential\",\"locale\":\"de\"}",
+ "vc.second-id.proof_types_supported","{\"jwt\":{\"proof_signing_alg_values_supported\":[\"ES256\"]}}"),
new OID4VCClient(null, "did:web:test.org",
List.of(new SupportedCredentialConfiguration()
.setId("first-id")
.setFormat(Format.JWT_VC)
- .setDisplay(new DisplayObject().setLocale("en").setName("First"))
+ .setDisplay(Arrays.asList(new DisplayObject().setLocale("en").setName("First")))
.setScope("AnotherCredential"),
new SupportedCredentialConfiguration()
.setId("second-id")
.setFormat(Format.SD_JWT_VC)
- .setDisplay(new DisplayObject().setLocale("de").setName("Second Credential"))
- .setScope("MyType")),
+ .setDisplay(Arrays.asList(new DisplayObject().setLocale("de").setName("Second Credential")))
+ .setScope("MyType")
+ .setProofTypesSupported(new ProofTypesSupported().setJwt(new ProofTypeJWT().setProofSigningAlgValuesSupported(Arrays.asList("ES256"))))),
null, null)
}
});
@@ -129,4 +128,4 @@ public class OID4VCClientRegistrationProviderTest {
OID4VCClientRegistrationProvider.fromClientAttributes("did:web:test.org", clientAttributes));
}
-}
\ No newline at end of file
+}
diff --git a/services/src/test/java/org/keycloak/protocol/oid4vc/model/ClaimsTest.java b/services/src/test/java/org/keycloak/protocol/oid4vc/model/ClaimsTest.java
new file mode 100644
index 0000000000..a9d012eb2a
--- /dev/null
+++ b/services/src/test/java/org/keycloak/protocol/oid4vc/model/ClaimsTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2024 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.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import org.junit.Test;
+import org.keycloak.util.JsonSerialization;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Francis Pouatcha
+ */
+public class ClaimsTest {
+
+ @Test
+ public void toJsonString() throws JsonProcessingException {
+ Claims claims = new Claims();
+ claims.put("firstName", new Claim());
+ claims.put("lastName", new Claim());
+ claims.put("email", new Claim());
+ String jsonString = claims.toJsonString();
+ JsonNode jsonNode = JsonSerialization.mapper.readTree(jsonString);
+ assertNotNull(jsonNode.get("firstName"));
+ assertNotNull(jsonNode.get("lastName"));
+ assertNotNull(jsonNode.get("email"));
+ }
+
+ @Test
+ public void fromJsonString() {
+ final String serializeForm = "{ \"firstName\": {}, \"lastName\": {}, \"email\": {} }";
+ Claims claims = Claims.fromJsonString(serializeForm);
+ assertNotNull(claims);
+ assertNotNull(claims.get("firstName"));
+ assertNotNull(claims.get("lastName"));
+ assertNotNull(claims.get("email"));
+ }
+
+ @Test
+ public void fromJsonStringDeepClaim() {
+ final String serializeForm = "{ \"firstName\": {\"mandatory\":false}, \"lastName\": {\"mandatory\":false}, \"email\": {\"mandatory\":true} }";
+ Claims claims = Claims.fromJsonString(serializeForm);
+ assertNotNull(claims);
+ assertNotNull(claims.get("firstName"));
+ assertFalse(claims.get("firstName").getMandatory());
+ assertNotNull(claims.get("lastName"));
+ assertFalse(claims.get("lastName").getMandatory());
+ assertNotNull(claims.get("email"));
+ assertTrue(claims.get("email").getMandatory());
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCIssuerWellKnownProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCIssuerWellKnownProviderTest.java
index 2ee6127216..73129a0d99 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCIssuerWellKnownProviderTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCIssuerWellKnownProviderTest.java
@@ -29,6 +29,8 @@ import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
public class OID4VCIssuerWellKnownProviderTest extends OID4VCTest {
@@ -52,6 +54,16 @@ public class OID4VCIssuerWellKnownProviderTest extends OID4VCTest {
assertTrue("The test-credential should be supported.", credentialIssuer.getCredentialsSupported().containsKey("test-credential"));
assertEquals("The test-credential should offer type VerifiableCredential", "VerifiableCredential", credentialIssuer.getCredentialsSupported().get("test-credential").getScope());
assertEquals("The test-credential should be offered in the jwt-vc format.", Format.JWT_VC, credentialIssuer.getCredentialsSupported().get("test-credential").getFormat());
+ assertNotNull("The test-credential can optionally provide a claims claim.", credentialIssuer.getCredentialsSupported().get("test-credential").getClaims());
+ assertNotNull("The test-credential claim firstName is present.", credentialIssuer.getCredentialsSupported().get("test-credential").getClaims().get("firstName"));
+ assertFalse("The test-credential claim firstName is not mandatory.", credentialIssuer.getCredentialsSupported().get("test-credential").getClaims().get("firstName").getMandatory());
+ assertEquals("The test-credential claim firstName shall be displayed as First Name", "First Name", credentialIssuer.getCredentialsSupported().get("test-credential").getClaims().get("firstName").getDisplay().get(0).getName());
+ assertEquals("The test-credential should offer vct VerifiableCredential", "VerifiableCredential", credentialIssuer.getCredentialsSupported().get("test-credential").getVct());
+ assertTrue("The test-credential should contain a cryptographic binding method supported named jwk", credentialIssuer.getCredentialsSupported().get("test-credential").getCryptographicBindingMethodsSupported().contains("jwk"));
+ assertTrue("The test-credential should contain a credential signing algorithm named ES256", credentialIssuer.getCredentialsSupported().get("test-credential").getCredentialSigningAlgValuesSupported().contains("ES256"));
+ assertTrue("The test-credential should contain a credential signing algorithm named ES384", credentialIssuer.getCredentialsSupported().get("test-credential").getCredentialSigningAlgValuesSupported().contains("ES384"));
+ assertEquals("The test-credential should display as Test Credential", "Test Credential", credentialIssuer.getCredentialsSupported().get("test-credential").getDisplay().get(0).getName());
+ assertTrue("The test-credential should support a proof of type jwt with signing algorithm ES256", credentialIssuer.getCredentialsSupported().get("test-credential").getProofTypesSupported().getJwt().getProofSigningAlgValuesSupported().contains("ES256"));
}));
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCTest.java
index 3b4500cefb..954dda47b4 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCTest.java
@@ -182,7 +182,14 @@ public abstract class OID4VCTest extends AbstractTestRealmKeycloakTest {
clientRepresentation.setAttributes(Map.of(
"vc.test-credential.expiry_in_s", "100",
"vc.test-credential.format", Format.JWT_VC.toString(),
- "vc.test-credential.scope", "VerifiableCredential"));
+ "vc.test-credential.scope", "VerifiableCredential",
+ "vc.test-credential.claims", "{ \"firstName\": {\"mandatory\": false, \"display\": [{\"name\": \"First Name\", \"locale\": \"en-US\"}, {\"name\": \"名前\", \"locale\": \"ja-JP\"}]}, \"lastName\": {\"mandatory\": false}, \"email\": {\"mandatory\": false} }",
+ "vc.test-credential.vct", "VerifiableCredential",
+ "vc.test-credential.cryptographic_binding_methods_supported", "jwk",
+ "vc.test-credential.credential_signing_alg_values_supported", "ES256,ES384",
+ "vc.test-credential.display.0","{\n \"name\": \"Test Credential\"\n}",
+ "vc.test-credential.proof_types_supported","{\"jwt\":{\"proof_signing_alg_values_supported\":[\"ES256\"]}}"
+ ));
clientRepresentation.setProtocolMappers(
List.of(
getRoleMapper(clientId),
@@ -347,4 +354,4 @@ public abstract class OID4VCTest extends AbstractTestRealmKeycloakTest {
}
}
-}
\ No newline at end of file
+}